/*
  Copyright 2021, 2022 V.R. Little & Rob Prior
  Contributors : 2023 e.vlad.engineer

**** Supports both huVVer-AVI (with installed huVVerLink & GaugeWidgets libraries),
  and M5Stack (with installed M5Stack & GaugeWidgets libraries) ***

  Permission is hereby granted, free of charge, to any person provided a copy of this software and associated documentation files
  (the "Software") to use, copy, modify, or merge copies of the Software for non-commercial purposes, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
  BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
  OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

// =============================================================//
#define Version 7.3 // This will be displayed on unit          //
// =============================================================//

// =============================================================//
// Specification of hardware is in the	                        //
// Library folder "DeviceDefines.h" file               			//
// Within the file, specify either of the following:            //
// #define DEVICE_HUVVER                                        //
// #define DEVICE_M5                                            //
// =============================================================//

// =============================================================//
// For software development, set DEBUG = 1 to                   //
// allow debugging messages.                                    //
// Otherwise, set DEBUG = 0 for proper in-service operation.    //
// =============================================================//
// const bool DEBUG = false;
const bool DEBUG = true;
// =============================================================//

// =============================================================//
// For software development, set DRAWTIME = 1 to                //
// allow for displaying instrument drawing time on USB Serial.  //
// Otherwise, set DRAWTIME = 0 for proper in-service operation. //
// =============================================================//
const bool DRAWTIME = false;
// const bool DRAWTIME = true;
// =============================================================//

// =============================================================//
// Network information (defined using the WiFi Manager):        //
// Use any valid Network name and Password up to 20 chars each. //
// This can be your Dynon SkyView login,                        //
// Or your own private network if you are are not using	SkyView	//
// and want to have one instrument configured as a Transmitter, //
// and one or more configured as Receivers.                     //
// =============================================================//

char ssid [21];
char pw [21];
const uint16_t port  = 49155;

char OTAssid[21];
char OTApassword[21];



#include <EncButton.h>
// buttons pins
// Подтяжка может быть внешней (режим пина нужно поставить INPUT) или внутренней (режим пина INPUT_PULLUP).
// init(npin, mode, dbTime);

// при нажатии 0(LOW)
// Button btn_left(39, INPUT, 150, true);// внешняя подтяжка
Button btn_left(34, INPUT);// внешняя подтяжка
// при нажатии 1(HIGH)
// Button btn_right(36, INPUT, 150, false);// внешняя подтяжка
Button btn_right(35, INPUT);// внешняя подтяжка

#include <ezButton.h>
ezButton button1(34);  // create ezButton object that attach to pin 34;
ezButton button2(35);  // create ezButton object that attach to pin 35;

// =============================================================//
#include "Options.h"
#include "Free_Fonts.h"
#include <WiFi.h>
#include <Preferences.h>
#include <nvs_flash.h>
#include "bableFish.h"
#include "Menus.h"
//#include "GaugeWidgets_Button.h"



#include "flightGauges.h"
// #include "logo.h"
#include <WiFiClient.h>

//#include <ESP32SJA1000.h> 	   //  https://github.com/sandeepmistry/arduino-CAN (new for version 6.3+)
//#include <AsyncTCP.h>          //  https://github.com/me-no-dev/AsyncTCP
//#include <ESPAsyncWebServer.h> //  https://github.com/me-no-dev/ESPAsyncWebServer
//#include <AsyncElegantOTA.h>   //  https://github.com/ayushsharma82/AsyncElegantOTA




#define WIFIPOWER WIFI_POWER_MINUS_1dBm

//
//CAN
//
// const long CAN_Bitrate = 250000;
const long CAN_Bitrate = 125000;
const boolean CAN_Loopback = false;
String CANRxBuffer = "";

const long UART_Bitrate = 115200;
const uint16_t SCREEN_X = 320;  // Screen sizes are set here.
const uint16_t SCREEN_Y = 240;

TFT_eSprite gdraw = TFT_eSprite(&VVLcd);

TFT_eSprite gdrawMenu = TFT_eSprite(&VVLcd);

Preferences preferences;
Menus myMenus;
Gauges myGauges;
EFISdata myEFISdata;
Flight myFlight;
WiFiUDP udp;

extern const uint8_t   MENU_DEFAULT;
extern const uint8_t   MENU_LIST;
extern const uint8_t   MENU_ACTION;
extern const uint8_t   MENU_SETUP;
const  uint32_t  	   BITRATE  = 115200;

// #include <EncButton.h>


//
// WiFi data. Depending on mode configuration, some of this may not be used.
//
IPAddress ip(192, 168, 4, 1);     // Connect to this IP
IPAddress broadcastIp (192, 168, 4, 255);
IPAddress netmask(255, 255, 255, 0);
char chipID[23];

// unsigned long sendTime; // Переменная для хранения времени отправки
// bool sendDataCompleted; // Флаг, указывающий, что отправка завершена
// bool alignmentCompleted; // Флаг, указывающий, что выравнивание завершено


bool buttonPressed = false; // Флаг, указывающий, что кнопка была нажата
bool sendDataDone = false; // Флаг, указывающий, что sendData выполнено
unsigned long startTime = 0; // Время начала отсчета

const uint16_t  LOOPTIME          = 62;    			// milliseconds.
const uint16_t  FRAMES            = 2000 / LOOPTIME;    // number of frames before link failure detected
const uint16_t  FLASHCOUNT        = 2; 					// number of frames for alarm flashing rate.
const uint8_t   WIFIOFF           = 0;     				// AKA Serial mode
const uint8_t   WIFIRX            = 1;
const uint8_t   WIFITX            = 2;
const uint8_t   WIFIOFFDEMO       = 3;     				// AKA Serial Demo mode
const uint8_t   WIFIRXDEMO        = 4;
const uint8_t   WIFITXDEMO        = 5;
const uint8_t   DIMMER_CH         = 4;     				// Dimmer PWM channel
const uint64_t  DIMMER_FREQ       = 8192;  				// Dimmer PWM frequency
const uint8_t   DIMMER_RES        = 12;    				// Dimmer PWM resolution
const uint8_t   SYSTEMPAGE        = 0;
const uint8_t   READMEPAGE        = 16;					// Index number
const bool      RS232             = false; 				// with RS232 transceiver
const bool      TTL               = true;  				// no RS232 transceiver
const uint16_t  RXBUFFER		  = 8192;				// serial receive FIFO size
const uint16_t  TXBUFFER		  = 8192;				// serial receive FIFO size
//
// ADARHS variables
//
int16_t  Pitch;
int16_t  Roll;
int16_t  Heading;    		// Not supported by OnSpeed
int16_t  AirSpeed;
int32_t  PressAlt;
int32_t  Altitude;   		// only D10A uses Altitude directly
int16_t  TurnRate;
int16_t  LatAccel;
int16_t  VertAccel 	= 10;
int16_t  AOA;
int16_t  VertSpeed;
int16_t  OAT;
int16_t  TASpeed;          	// Not supported by Garmin or OnSpeed
int16_t  Baro      	= 242; 	// Not supported by OnSpeed
int32_t  DensAlt;          	// Not supported by Garmin or OnSpeed
int16_t  WindDir   	= -1;   // Not supported by Garmin or OnSpeed
int16_t  WindSpeed 	= -1;   // Not supported by Garmin or OnSpeed

//
// Derived variables, computed herein
//
int16_t  GroundTrack		= -1000;
int16_t  GroundSpeed    	= -1;
int16_t  DriftAngle     	= -1000;

//
// OnSpeed additional IMU constants and variables
//
int16_t  OnSpeedAOA;
int16_t  FlightPath			= -1000;
int16_t  FlapPos;
int16_t  OSStallWarnAOA    	= 20;
int16_t  OSSlowAOA        	= 15;
int16_t  OSFastAOA         	= 10;
int16_t  OSTonesOnAOA      	= 5;
int16_t  gOnsetRate       	= 0;
int16_t  SpinRecoveryCue   	= 0;
int16_t  DataMark          	= 0;

//
// The following are used for the OnSpeed Energy Display
//
int16_t PercentLift; // Actually displayed AOA numbers

const float aoaSmoothingAlpha 	= 0.7f;  //1 = max smoothing, 0.01 no smoothing.
const float slipSmoothingAlpha 	= 0.5f;  //1 = max smoothing, 0.01 no smoothing.
const float decelSmoothingAlpha = 0.04f; //1 = max smoothing, 0.01 no smoothing.
const float serialRate			= 0.1f;  // 10 hz;

int16_t  Slip;
uint16_t ballSize				= 34; //for Slip Filter
float    SmoothedLateralG;
float 	 SmoothedAOA			= 0.00;
float 	 DecelRate;
float 	 SmoothedDecelRate 		= 0.0;
float    gHistory[300];
int16_t  gHistoryIndex 			= 0;

//
// System variables (Dynon Only)
//
int16_t  HdgBug     = -1;
int16_t  AltBug     = -10000;
int16_t  ASBug      = -1;
int16_t  VSBug      = -1000;
int16_t  Course     = -1;
uint8_t  CDSTyp;
uint8_t  CDSPor;
uint8_t  CDScal;
int16_t  CDDefl     = -100;
int16_t  GlideS     = -100;
uint8_t  APEngd;
uint8_t  APRMod;
uint8_t  APPMod;
int16_t  APRFor;
int16_t  APRPos     = -10000;
uint8_t  APRSlp;
int16_t  APPFor;
int16_t  APPPos     = -10000;
uint8_t  APPSlp;
int16_t  APYFor;
int16_t  APYPos     = -10000;
uint8_t  APYSlp;
uint8_t  XPStat;
uint8_t  XPRep;
uint8_t  XPID;
uint16_t XPCode     = 00000;

//
// Engine variables
//
int16_t  OilP;                  // -100  invalid
int16_t  OilT;                  // -1000 invalid
int16_t  RpmL;
int16_t  RpmR;
int16_t  Map;                   // -100  invalid
int16_t  FuelF1;                // -100  invalid
int16_t  FuelF2;                // -100  invalid
int16_t  FuelP;                 // -100  invalid
int16_t  FuelL;                 // -100  invalid
int16_t  FuelR;                 // -100  invalid
int16_t  FuelRem;               // -100  invalid
int16_t  Volts1;                // -100  invalid
int16_t  Volts2;                // -100  invalid
int16_t  Amps;                  // -1000 invalid
uint32_t HobbsT;
uint32_t TachT;
int16_t  TCpl[14];              // -1000 invalid
int16_t  GpIn[13];              // -10000invalid
int8_t   CtctIn[16];            // -1    invalid
int16_t  PercentPwr;            // -100  invalid
char     LeanState;

//
// Top level operating states
//
int32_t  SerialPort;           // change from System Configuration menu or by holding down Menu button for 4 seconds.
uint16_t operationMode;        // current operating mode. Combinations of serial, wifi and demo modes.
bool     updateMode;           // pending OTA update mode
bool     managerMode;          // pending WiFi manager mode
uint16_t Brightness;           // screen brightness
uint64_t loopTime = millis();  // main programe loop timing variables
uint64_t currentMillis = millis();
uint64_t previousMillis = millis();
int16_t  displayMode = 0;      // instrument page select
int8_t   displayDirection = 1; // for navigating active instruments
uint16_t flashCount = 0;
bool     flashFlag;            // allows flashing of graphics or numerics at the rate set by LOOPTIME
int16_t  frameCount = FRAMES;  // number of sequential frames missing before display is disabled
bool     dimDirection = true;  // allows for increment or decrement of screen brightness.
int16_t	 toggle = 0;

IPAddress OTAip(192, 168, 4, 1);     // Connect to this IP
IPAddress OTAbroadcastIp (192, 168, 4, 255);
IPAddress OTAnetmask(255, 255, 255, 0);

//AsyncWebServer server(80);

void setup()
{
  
  // т.к правая кнопка подтянута к 3.3 вольт
  //btn_left.setButtonLevel(HIGH);
  //btn_right.setButtonLevel(HIGH);
  //pinMode(34, INPUT);
  //pinMode(35, INPUT);

  btn_right.setButtonLevel(HIGH);
  // click timeout (button)
  // можно отрегулировать быстроту обработки клика
  btn_left.setClickTimeout(1000);
  btn_right.setClickTimeout(1000);
  // таймаут удержания (кнопка)
  btn_left.setHoldTimeout(500);
  btn_right.setHoldTimeout(500);
  // таймаут гашения дребезга кнопки (кнопка)
  // btn_left.setDebTimeout(20);
  // btn_right.setDebTimeout(20);
  //
  //digitalWrite (TFT_BL, LOW);
  // digitalWrite (TFT_LED_PIN, LOW);
  //pinMode (TFT_BL, OUTPUT);
  // pinMode (TFT_LED_PIN, OUTPUT);

  pinMode(INT_PIN, OUTPUT);
  digitalWrite(INT_PIN, LOW);

  VVbegin();

  //
  // Force display off and initialize
  //

  // Init the back-light LED PWM

  // myFlight.BacklightInit(TFT_LED_PIN, DIMMER_CH, DIMMER_FREQ, DIMMER_RES);
  //myFlight.BacklightInit(TFT_BL, DIMMER_CH, DIMMER_FREQ, DIMMER_RES);
  //myFlight.Backlight(0);

  //
  // Display splash screen
  //
  VVLcd.setSwapBytes(true);
  VVLcd.fillScreen(TFT_BLACK);
  myFlight.Backlight(0);

  preferences.begin ("huVVer-app");
  Brightness = preferences.getUInt ("Brightness", 1023);
  preferences.end ();

  // VVLcd.pushImage(0, 0, logoWidth, logoHeight, Logo);

  // Вывод надписи "flight gauges" на спрайт
  // gdraw.setColorDepth(8);
  // gdraw.createSprite(SCREEN_X, SCREEN_Y);
  // gdraw.fillScreen (TFT_BLACK);
  VVLcd.setTextColor(TFT_WHITE);
  VVLcd.setTextDatum(MC_DATUM);
  VVLcd.setCursor(10, 60);
  VVLcd.setTextSize(3);
  VVLcd.println("Flight gauges");
  VVLcd.setCursor(20, 150);
  VVLcd.setTextSize(3);
  VVLcd.println("for AVIATION");
  // gdraw.pushSprite(0, 0);  // Отображение спрайта на дисплее

  for (float i = 1.0f; i <= Brightness; i += (float)Brightness / 200.0f)
  {
    myFlight.Backlight(i);
    delay (3);
  }

  delay (150);
  for (float i = 1.0f; i <= Brightness; i += (float)Brightness / 200.0f)
  {
    myFlight.Backlight(Brightness - i);
    delay (3);
  }

  delay (250);

  WiFi.persistent (false); // very important... we are using program constants!

  gdraw.setColorDepth(8);
  gdraw.createSprite(SCREEN_X, SCREEN_Y);
  gdraw.fillSprite (TFT_BLACK);
  gdraw.pushSprite (0, 0);

  // gdrawMenu.setColorDepth(8);
  // gdrawMenu.createSprite(SCREEN_X, SCREEN_Y);
  // gdrawMenu.fillSprite (TFT_BLACK);
  // gdrawMenu.setTextColor(TFT_WHITE);
  // gdrawMenu.setTextDatum(MC_DATUM);
  // gdrawMenu.setCursor(160 - 30, 120 - 30);
  // gdrawMenu.setTextSize(4);

  //
  // Preset outputs
  //
  // digitalWrite (PIN_AUDL, LOW); digitalWrite (PIN_AUDR, LOW);
  // pinMode (PIN_AUDL, OUTPUT); pinMode (PIN_AUDR, OUTPUT);

  // digitalWrite (PIN_OC1, LOW); digitalWrite (PIN_OC2, LOW);
  // pinMode (PIN_OC1, OUTPUT); pinMode (PIN_OC2, OUTPUT);

  //
  // Get Active Serial Port - при старте устройства, идет загрузка из преференсов
  //
  preferences.begin ("getSetup", false);
  SerialPort = preferences.getInt (SERIALPORT, 2);
  preferences.end();

  Serial2.setRxBufferSize (RXBUFFER);
  switch (SerialPort)
  {
    case 1: // On the M5Stack, this takes over the I2C port.
      Serial1.setRxBufferSize (RXBUFFER);
      Serial1.setTxBufferSize (TXBUFFER);
      Serial1.begin (BITRATE, SERIAL_8N1, PIN_RX1, PIN_TX1, RS232);
      break;
    case 2:
      Serial2.setRxBufferSize (RXBUFFER);
      Serial2.setTxBufferSize (TXBUFFER);
      Serial2.begin (BITRATE, SERIAL_8N1, PIN_RX2, PIN_TX2, RS232);
      break;
    case -1: // On the M5Stack, this takes over the I2C port.
      Serial1.setRxBufferSize (RXBUFFER);
      Serial1.setTxBufferSize (TXBUFFER);
      Serial1.begin (BITRATE, SERIAL_8N1, PIN_RX1, PIN_TX1, TTL);
      break;
    case -2:
      Serial2.setRxBufferSize (RXBUFFER);
      Serial2.setTxBufferSize (TXBUFFER);
      Serial2.begin (BITRATE, SERIAL_8N1, PIN_RX2, PIN_TX2, TTL);
      break;
    default:
      break;
  }

  //
  // Power-on mode configuration.
  //
  myFlight.LoadEnables(); // load all of the system settings

//  myMenus.loadPrefs = true;

  preferences.begin ("huVVer-app", false);

// Get the value of the counter key saved on preferences. 
// If it doesn’t find any value, it returns 0 by default
// (which happens when this code runs for the first time).
  Magnet = preferences.getUInt("Magnet", 1);
  FilterNum = preferences.getUInt("FilterNum", 0);
  OffsetPitch = preferences.getUInt("OffsetPitch", 5);
  myEFISdata.sendData(false, Magnet, FilterNum, thousands, hundreds, tens, units, OffsetPitch);
  

  // Magnet = myMenus.LoadPrefs("getAI", "Magnet");
 
  operationMode = 1;

  if (myFlight.WiFiRX == 1 && myFlight.WiFiTX == 0 && myFlight.demoMode == 0)
  {
    operationMode = WIFIRX;
  }

  if (myFlight.WiFiRX == 0 && myFlight.WiFiTX == 1 && myFlight.demoMode == 0)
  {
    operationMode = WIFITX;
  }

  if (myFlight.WiFiRX == 0 && myFlight.WiFiTX == 0 && myFlight.demoMode == 0)
  {
    operationMode = WIFIOFF; // Serial mode
  }

  if (myFlight.WiFiRX == 1 && myFlight.WiFiTX == 1 && myFlight.demoMode == 0)
  {
    operationMode = WIFIOFF; // Serial mode
  }

  if (myFlight.WiFiRX == 1 && myFlight.WiFiTX == 0 && myFlight.demoMode == 1)
  {
    operationMode = WIFIRXDEMO;
  }

  if (myFlight.WiFiRX == 0 && myFlight.WiFiTX == 1 && myFlight.demoMode == 1)
  {
    operationMode = WIFITXDEMO;
  }

  if (myFlight.WiFiRX == 0 && myFlight.WiFiTX == 0 && myFlight.demoMode == 1)
  {
    operationMode = WIFIOFFDEMO; // Serial Demod mode
  }

  if (myFlight.WiFiRX == 1 && myFlight.WiFiTX == 1 && myFlight.demoMode == 1)
  {
    operationMode = WIFIOFFDEMO; // Serial Demo mode
  }

  preferences.putUInt("operationMode", operationMode);
  preferences.end();  // huVVer-app

  //Buttons A ([]) and B [O] held down at bootup forces a System Restore
  //Нажав и удерживая одновременно две кнопки и перезагрузив, получим сброс настроек до заводских
  if (btn_left.hold() && btn_right.hold())
  // while (btn_left.busy() && btn_right.busy())
    // if (ButtonA.read() && ButtonB.read())
  {

    if (DEBUG) Serial.println ("SYSTEM SETTINGS RESTORED ");

    //
    // Restore System Configuration setting to factory default.
    // This can eliminate the problem of sticking at boot in WiFi Rx mode
    // with no WiFi transmitter available.
    //
    myFlight.RestoreAll();

    VVLcd.setFreeFont(FSSB12);
    VVLcd.setTextColor (TFT_WHITE);
    VVLcd.setTextDatum (MC_DATUM);
    VVLcd.drawString ("System Settings Restored", SCREEN_X / 2, SCREEN_Y / 2);

    delay (5000);
  }

  //
  // V3.00 Adds a WiFi credential manager.
  //
  preferences.begin ("getSetup", false);
  int32_t WiFiManager = preferences.getInt (WIFIMANAGER, 0);
  preferences.putInt (WIFIMANAGER, 0);  // clear upDate
  preferences.end();

  if (WiFiManager == 1)
  { // If we are in WiFi Manager mode
    managerMode = true;   // this will be cleared in flightGauges.h
  }

  //
  // Holding Menu button A ([]) down during splash screen brings up credential manager page.
  // Or, you can enable WiFi Manager in System Configuration menu.
  //
  // if ((ButtonA.isReleased() && ButtonA.read() == false) || managerMode)
  /* MenuBtn - левая кнопка
    SelectBtn - правая кнопка
    read = 1 когда левая кнопка НАЖАТА, а правая не нажата - потому, что левая кнопка при нажатии дает 3.3 вольт(1), а правая дает GND(0)
    правая кнопка в ненажатом состоянии дает 1, в нажатом 0, левая наоборот. */
  // MenuBtn.read() = 1 когда нажата
  // MenuBtn.read() = 0 когда отпущена
  // SelectBtn.read() = 1 когда отпущена
  // SelectBtn.read() = 0 когда нажата
  // Нажав левую кнопку и удержав при перезагрузке, попадаем в меню Wi-Fi
  // if ((MenuBtn.read() && SelectBtn.read()) || managerMode)
  if ((btn_left.hold() && !btn_right.hold()) || managerMode) 
  // while ((btn_left.busy() && !btn_right.busy()) || managerMode)
  {
    //
    // Call Keyboard(true) for initialization of WiFi credentials
    //

    // initialize brightness, a bug fix required after a RestoreAll
    preferences.begin ("huVVer-app", false); // restore brightness during setup
    preferences.putUInt ("Brightness", Brightness);
    preferences.end();

    myFlight.Keyboard(true);
    bool done = false;

    preferences.begin ("getSetup", false);
    //int32_t WiFiManager = preferences.getInt (WIFIMANAGER, 0);
    preferences.putInt (WIFIMANAGER, 0);  // clear upDate
    preferences.end();

    while (!done)
    {
      // VVupdate();
      gdraw.fillSprite (TFT_BLACK);
      gdraw.setTextColor(TFT_WHITE);

      //
      // Call Keyboard(false) for editing WiFi credentials.
      // 'done' state indicates that credentials have been saved,
      // otherwise the loop continues.
      //
      done = myFlight.Keyboard(false);

      gdraw.pushSprite (0, 0);
      delay(50);

      if (done)
      {
        myFlight.PrintMessage ("WiFi Config Saved", 2000, TFT_WHITE, TFT_RED);
      }

      // if (SelectBtn.releasedFor(4000))
      if (btn_right.hold())//не работает в setup(), нужен опрос в loop()
      {
        myFlight.PrintMessage ("WiFi Config Ended", 2000, TFT_WHITE, TFT_RED);
        done = true;
      }
    }

    //
    // Fade out and blank screen
    //
    for (float i = 1.0f; i <= Brightness; i += (float)Brightness / 200.0f)
    {
      myFlight.Backlight(Brightness);
      delay (5);
    }
    gdraw.fillSprite (TFT_BLACK);
    gdraw.pushSprite (0, 0);
    ESP.restart();
  } // end WiFi Manager

  preferences.begin ("huVVer-app", false);
  preferences.getString("EFIS-ID", "Wokwi-GUEST")		.toCharArray(ssid, 21);
  preferences.getString("EFIS-PW", "")	.toCharArray(pw, 21);
  preferences.getString("UNIT-ID", "huVVer-AVI")	.toCharArray(OTAssid, 21);
  preferences.getString("UNIT-PW", "huVVer-AVI")	.toCharArray(OTApassword, 21);

  // operationMode = preferences.getUInt("operationMode", WIFIOFF);
  // демо режим, при котором отсутствуют красные рамки Х
  operationMode = WIFIOFFDEMO;
  // operationMode = WIFITX;
          // case 4:
          // if (myFlight.aiEnable)
          // выбор дисплея по умолчанию при загрузке - preferences в wokwi не работают
  displayMode = preferences.getInt("displayMode", 4);
  Brightness = preferences.getUInt("Brightness", 1023);
  myFlight.Backlight(Brightness);
  preferences.end();

  //================================================================//
  // Main Configuration Menu.                                       //
  //================================================================//

  {
    // Display operating mode on screen
    gdraw.setFreeFont(FSSB12);
    gdraw.setTextColor (TFT_GREEN);
    gdraw.setTextDatum (MC_DATUM);

    String portId = "Serial " + String(SerialPort);

    switch (operationMode)
    {
      case WIFIOFF:
        break;
      case WIFIRX:
        portId = "WiFi Rx";
        break;
      case WIFITX:
        portId = "WiFi Tx";
        break;
      case WIFIOFFDEMO:
        portId += " Demo";
        break;
      case WIFIRXDEMO:
        portId = "WiFi Rx Demo";
        break;
      case WIFITXDEMO:
        portId = "WiFi Tx Demo";
        break;
      default:
        operationMode = WIFIOFF;
        break;
    }


    preferences.begin ("getSetup", false);
    if (preferences.getInt (UPDATE, 0) > 0) portId = "OTA Update";
    preferences.end();

    gdraw.setTextColor (TFT_WHITE);
    gdraw.drawString (portId, SCREEN_X / 2, SCREEN_Y / 2);

  } // Main Configuration Menu.

  gdraw.pushSprite (0, 0);
  delay (1500); // duration of splash screen display

  snprintf(chipID, 23, "%04X%08X", (uint16_t)(ESP.getEfuseMac() >> 32), (uint32_t)ESP.getEfuseMac());
  Serial.println (chipID);

  //
  // WiFi operating mode selection
  //
  if (operationMode == WIFIRX || operationMode == WIFIRXDEMO)
  {
    //  WiFi.disconnect();
    WiFi.mode(WIFI_STA);
    WiFi.begin(ssid, pw, 6);

    //
    // Adust WiFi power levels as required for the application.
    // Note, for bench testing multiple devices, keep the power low to prevent crashes
    // due to RF interference.
    //
    WiFi.setTxPower(WIFIPOWER);
    delay (1000);

    //
    // The following tries to connect to the access point,
    // and if it can't after 60 seconds, restarts the entire device.
    //
    uint16_t wifiLoop = 60;
    while (WiFi.status() != WL_CONNECTED)
    {
      Serial.println("WiFi Failed");
      delay(1000);
      //нажать правую кнопку
      // if (!SelectBtn.read())
      {
        preferences.begin("getSetup", false);
        preferences.putInt (P_WIFIRX, 0);
        preferences.end();
        myFlight.Backlight(0);
        ESP.restart();
      }

      wifiLoop--;
      if (wifiLoop == 0)
      {
        myFlight.Backlight(0);
        ESP.restart();  // something is wrong!
      }
    }
    udp.begin(port);
    Serial.println ("WiFi Station Connected, listening for UDP Broadcasts");
    Serial.print ("Local IP address: "); Serial.println (WiFi.localIP());
  }

  //
  // The following sequence is absolutely critical for reliable operation
  // and prevents guru meditation errors.
  // https://github.com/espressif/arduino-esp32/issues/2283#
  //
  if (operationMode == WIFITX || operationMode == WIFITXDEMO)
  {
    WiFi.mode(WIFI_AP);
    WiFi.softAP(ssid, pw); // configure ssid and password for softAP
    uint16_t wifiLoop = 60;
    while (!(WiFi.softAPIP() == ip))
    {
      WiFi.softAPConfig(ip, ip, netmask); // configure ip address for softAP
      delay (1000);
      wifiLoop--;
      if (wifiLoop == 0)
      {
        myFlight.Backlight(0);
        ESP.restart();  // something is wrong!
      }
    }

    //
    // Adjust WiFi power levels as required for the application.
    // Note, for bench testing multiple devices, keep the power low to prevent crashes
    // due to RF interference.
    //
    WiFi.setTxPower(WIFIPOWER);

    Serial.printf("\nStarting UDP Broadcast Server...");

    Serial.printf("Using UDP/IP over Wifi, this Server will broadcast any data received on the serial port.\n");
    Serial.printf("Connect to ssid:%s, password:%s\n", ssid, pw);
    Serial.print("IP Address:"); Serial.print (ip); Serial.print(", Port:"); Serial.println(port);
  }

  //
  // Load the instrument enable preferences.
  //
  myFlight.LoadEnables ();
  if (DEBUG) Serial.println ("Loaded Enables...");

  //
  // load the default instrument preferences.
  //
  myMenus.loadPrefs = true;
  // myMenus.LoadPrefs("getAI", Values[1][5]);
  // myEFISdata.sendData(false, Magnet, FilterNum, thousands, hundreds, tens, units, OffsetPitch);
  

  //
  // OTA Updater
  //
  preferences.begin ("getSetup", false);
  int32_t upDate = preferences.getInt (UPDATE, 0);
  preferences.putInt (UPDATE, 0);  // clear upDate
  preferences.end();

  if (upDate == 1) // If we are in upDate mode
  {
    updateMode = true;  // this will be cleared in flightGauges.h

    // Start a Soft Access Point
    WiFi.disconnect();
    WiFi.softAP(OTAssid, OTApassword); // configure ssid and password for softAP
    uint16_t wifiLoop = 60;
    while (!(WiFi.softAPIP() == OTAip))
    {
      WiFi.softAPConfig(OTAip, OTAip, OTAnetmask); // configure ip address for softAP
      delay (1000);
      wifiLoop--;
      if (wifiLoop == 0)
      {
        myFlight.Backlight(0);
        ESP.restart();  // something is wrong!
      }
    }

    Serial.println("");
    Serial.print("Connect to ");
    Serial.println(ssid);
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());

//    server.on("/", HTTP_GET, [](AsyncWebServerRequest * request)
//    {
//      request->send(200, "text/plain", "huVVer-AVI OTA Updater.\n");
//    });

//    AsyncElegantOTA.begin(&server);    // Start ElegantOTA
//    server.begin();
//    Serial.println("HTTP server started");
  }

  //
  // CAN setup
  //
  //CAN.setPins (PIN_CANRX, PIN_CANTX);
  //pinMode (PIN_CANRS, INPUT);  // float for edge control
  // if pins define -1 we have errors
  // E (6409) gpio: gpio_set_direction(274): GPIO number error
  // E (6410) gpio: gpio_set_direction(274): GPIO number error

//  while (!CAN.begin(CAN_Bitrate)) {
//    Serial.println("Starting CAN failed!");
//    ESP.restart(); // try again
//  }

  //
  // set up CAN modes
  //
  //if (CAN_Loopback) CAN.loopback();

  //
  // 0x000 is reserved to send embedded 8-byte commands:
  // DIMnVVVV (dimmer value)
  //   DIM --> Dimmer command
  //   n = 0 analog dimmer value
  //   n = 1 persistent analog dimmer value
  //   n = 2 digital dimmer value
  //   n = 3 persistent digital dimmer value
  //   VVVV = 1...4095 brightness value
  //

//  CAN.filter(0, 0x7ff);

}
//=========End of Setup=============//

void loop()
{
  //btn_left.tick(!digitalRead(34));
  //btn_right.tick(!digitalRead(35));
    btn_left.tick();
    btn_right.tick();
    button1.loop();
    button2.loop();

  long unsigned int time1 = micros();
  static int16_t msgTime = 0;
  static const char *myMessage = "20 character message";

  // VVupdate();

  //AsyncElegantOTA.loop();

  //
  // Check WiFi operating state every few seconds
  //
  if (operationMode == WIFIRX || operationMode == WIFIRXDEMO)
  {
    currentMillis = millis();
    if ((currentMillis - previousMillis) >= 5000)
    {
      if (WiFi.status() != WL_CONNECTED)
      {
        Serial.printf("WiFi disconnected, reconnecting...\n");
        WiFi.disconnect();
        WiFi.mode(WIFI_AP_STA);
        WiFi.begin(ssid, pw, 6);
        WiFi.setTxPower(WIFIPOWER);
      }
      previousMillis = currentMillis;
    }
  }

  //переключение на "лету" нажать МЕНЮ() и потом снова и удержать 4 секунды
  // if (MenuBtn.pressedFor(10000))
  if (btn_left.hold(4))
  {
    if (operationMode == WIFIRX || operationMode == WIFIRXDEMO)
    {
      WiFi.disconnect();
      if (operationMode == WIFIRX) operationMode = WIFIOFF;
      if (operationMode == WIFIRXDEMO) operationMode = WIFIOFFDEMO;

      preferences.begin ("huVVer-app", false);
      preferences.putUInt ("operationMode", operationMode);
      preferences.end();
    }

    else
    {
      switch (SerialPort)
      {
        case -2:
          Serial2.end();
          Serial1.setRxBufferSize (RXBUFFER);
          Serial1.setTxBufferSize (TXBUFFER);
          Serial1.begin (BITRATE, SERIAL_8N1, PIN_RX1, PIN_TX1, TTL);
          SerialPort = -1;
          break;
        case -1:
          Serial1.end();
          Serial2.setRxBufferSize (RXBUFFER);
          Serial2.setTxBufferSize (TXBUFFER);
          Serial2.begin (BITRATE, SERIAL_8N1, PIN_RX2, PIN_TX2, TTL);
          SerialPort = -2;
          break;
        case 1:
          Serial1.end();
          Serial2.setRxBufferSize (RXBUFFER);
          Serial2.setTxBufferSize (TXBUFFER);
          Serial2.begin (BITRATE, SERIAL_8N1, PIN_RX2, PIN_TX2, RS232);
          SerialPort = 2;
          break;
        case 2:
          Serial2.end();
          Serial1.setRxBufferSize (RXBUFFER);
          Serial1.setTxBufferSize (TXBUFFER);
          Serial1.begin (BITRATE, SERIAL_8N1, PIN_RX1, PIN_TX1, RS232);
          SerialPort = 1;
          break;
        default:
          break;
      }

      // Save new selection
      preferences.begin ("getSetup", false);
      preferences.putInt (SERIALPORT, SerialPort);
      preferences.end();
    }

    myFlight.PrintMessage ("Serial " + String(SerialPort), 2000, TFT_WHITE, TFT_WHITE);

    // Establish top level selection and refresh
    myMenus.menuState = MENU_DEFAULT;  // Back to instrument display
    myMenus.loadPrefs = true;
    //если выставлял MenuBtn.read() без ! то смена приборов была очень шустро
    // while (!MenuBtn.read());
    // while (btn_left.read());//держишь, сменяется порт и пропадает сообщение когда отпускаешь, работает с новой либой кнопок и без этого
  }

  //
  // Top level (Default) menu handling.
  //
  switch (myMenus.menuState)
  {
      //btn_left.tick(!digitalRead(34));
      //btn_right.tick(!digitalRead(35));
    btn_left.tick();
    btn_right.tick();
    button1.loop();
    button2.loop();
    case MENU_DEFAULT: // Default (top level)

      // if (!MenuBtn.wasPressed() && SelectBtn.releasedFor(6000))
      if (btn_left.hold() && btn_right.hold())//перезагрузка на главном меню
      {
        myFlight.Backlight(0);
        ESP.restart();
      }


      //
      // Press Menu Button A ([]) to enter list menu.
      // Вход в меню настроек - удерживаем левую кнопку 3 секунды
      // if (MenuBtn.pressedFor(1000) && !SelectBtn.read())
      // if (btn_left.hold(1))//вход в меню параметров
      if(button2.isPressed())
      {
        myMenus.menuState = MENU_LIST;
        myMenus.loadPrefs = true;

        preferences.begin ("huVVer-app", false); // restore brightness during setup
        Brightness = preferences.getUInt("Brightness", 1023);
        myFlight.Backlight(Brightness);
        preferences.end();

        if (DEBUG) Serial.println ("Menu List");
      }

  // Проверка, была ли кнопка нажата и sendData еще не выполнено
if (button1.isPressed() && !buttonPressed && (displayMode == 4)) {
// if (btn_left.hold() && !buttonPressed && (displayMode == 4)) {
  // myMenus.menuState = MENU_LIST;
  // myEFISdata.sendData();
  // myEFISdata.sendData(true, false, 1, 0, 1, 3, +0);// ALIGN, no MAG, 1013 hPa, +5 OffsetPitch
  myEFISdata.sendData(true, Magnet, FilterNum, thousands, hundreds, tens, units, OffsetPitch);
  buttonPressed = true; // Установка флага, указывающего, что кнопка была нажата
  startTime = millis(); // Запись текущего времени
}

if (buttonPressed && !sendDataDone) {
  if (millis() - startTime <= 5000) {
    VVLcd.setTextColor(TFT_RED);
    VVLcd.setTextDatum(MC_DATUM);
    VVLcd.setCursor((SCREEN_X / 2) - 40, SCREEN_Y / 2);
    VVLcd.setTextSize(1);
    VVLcd.println("ALIGN");
  } else if (millis() - startTime >= 1000) {
    buttonPressed = false;
    sendDataDone = true; // Установка флага, указывающего, что sendData выполнено
    startTime = millis(); // Запись текущего времени
  }
}

if (sendDataDone) {
  if (millis() - startTime <= 1000) {
    VVLcd.setTextColor(TFT_GREEN);
    VVLcd.setTextDatum(MC_DATUM);
    VVLcd.setCursor((SCREEN_X / 2) - 64, SCREEN_Y / 2);
    VVLcd.setTextSize(1);
    VVLcd.println("ALIGN OK");
  } else if (millis() - startTime >= 1000) {
    sendDataDone = false; // Сброс флага, указывающего, что sendData выполнено
  }
}

    // if (btn_left.hold(5))//вход в меню setup
//     if(button1.isPressed())
//     {

// unsigned long sendTime = 0; // Переменная для хранения времени отправки
// bool sendDataCompleted = false; // Флаг, указывающий, что отправка завершена
// bool alignmentCompleted = false; // Флаг, указывающий, что выравнивание завершено

// unsigned long currentTime = millis(); // Получение текущего времени

//   // Отправка данных один раз
//   if (!sendDataCompleted) {
//     // myMenus.menuState = MENU_SETUP;
//     myEFISdata.sendData();
//     //myMenus.loadPrefs = true;
//     sendTime = currentTime; // Запоминаем время отправки
//     sendDataCompleted = true; // Установка флага, указывающего, что отправка завершена
//   }




//   // Проверка, прошло ли 10 секунд с момента отправки
//   if (sendDataCompleted && currentTime - sendTime >= 10000) {
//     alignmentCompleted = true; // Установка флага, указывающего, что выравнивание завершено
//   }

//   // Вывод надписи и изменение цвета
//   if (alignmentCompleted) {
//     VVLcd.setTextColor(TFT_GREEN);
//     VVLcd.setTextDatum(MC_DATUM);
//     VVLcd.setCursor((SCREEN_X / 2) - 40, SCREEN_Y / 2);
//     VVLcd.setTextSize(1);
//     VVLcd.println("ALIGN OK");
//   } else {
//     VVLcd.setTextColor(TFT_RED);
//     VVLcd.setTextDatum(MC_DATUM);
//     VVLcd.setCursor((SCREEN_X / 2) - 40, SCREEN_Y / 2);
//     VVLcd.setTextSize(1);
//     VVLcd.println("ALIGN");
//   }


//         preferences.begin ("huVVer-app", false); // restore brightness during setup
//         // Brightness = preferences.getUInt("Brightness", 1023);
//         Brightness = 1023;
//         myFlight.Backlight(Brightness);
//         preferences.end();

//         if (DEBUG) Serial.println ("Setup List");
//   }


      { // Modify the scope to keep variables local
        static bool selectMode = false;   // used to automatically save brightness, one-shot state.
        static uint64_t buttonTimer = 0;  // timer for brightness saving.
        VVLcd.setTextDatum (MC_DATUM);
        VVLcd.setFreeFont(FSSB12);

        // Press Select button B(O) to increase or decrease brightness

        // if (SelectBtn.wasPressed() && !MenuBtn.wasPressed() && !FwdBtn.wasPressed() && !BackBtnWasPressed())
        // if (SelectBtn.releasedFor(10000) && !MenuBtn.wasPressed() && !BackBtnWasPressed())
        if (btn_right.hold(1))//смена направления яркости - не работает
        {
          // Brightness up
          if (dimDirection)
          {
            /*             if (Brightness < 4096)
                        {
                          Brightness *= 2; // brightness
                          if (Brightness >= 4095) Brightness = 4095;

            			  if (myFlight.dimChannel >= 1 && myFlight.dimChannel <= 4)
            			  {
            				myFlight.Backlight (Brightness, myFlight.dimChannel, 1); // share temporary Brightness information
            			  }

                          msgTime = 4; //loops
                          myMessage = "Dim ++";
                        } */

            if (Brightness >= 4095)
            {
              Brightness = 4095;
              msgTime = 8; //loops
              myMessage = "Max Brightness";
              dimDirection = !dimDirection; // automatically toggle up/down direction
            }
          }

          // Brightness down

          else {
            if (Brightness > 1)
              /*             {
                            Brightness = Brightness/2;

              			  if (myFlight.dimChannel >= 1 && myFlight.dimChannel <= 4)
              			  {
              				myFlight.Backlight (Brightness, myFlight.dimChannel, 1); // share temporary Brightness information
              			  }

                            msgTime = 4; //loops
                            myMessage = "Dim--";
                          } */
              if (Brightness <= 1)
              {
                Brightness = 1;
                msgTime = 12; //loops
                myMessage = "Min Brightness";
                dimDirection = !dimDirection; // automatically toggle up/down direction
              }
          }

          buttonTimer = millis(); // zero the brightness save timer on every Select button push
          selectMode = true;
        }

        //кнопка А нажимается для изменения яркости(увеличение по кругу)
        // if (SelectBtn.releasedFor(1000) && !MenuBtn.wasPressed())
        if (btn_left.hold(3)) //яркость ++ по кругу
        {
          // if (SelectBtn.wasPressed() && !MenuBtn.wasPressed() && !FwdBtn.wasReleased() && !BackBtnWasPressed()) {
          Brightness *= 2; // brightness up
          if (Brightness == 4096) Brightness = 4095;
          if (Brightness > 4096) Brightness = 1;
          if (Brightness == 0) Brightness = 1;

          // VVLcd.setBrightness (Brightness);
          myFlight.Backlight (Brightness);

          if (Brightness == 4095) {
            VVLcd.setTextDatum (MC_DATUM);
            VVLcd.setFreeFont(FSSB12);
            char *buffer = "MAX BRIGHTNESS";

            VVLcd.setTextColor (TFT_BLACK);
            VVLcd.drawString(buffer, 161, 121, GFXFF);

            VVLcd.setTextColor (TFT_WHITE);
            VVLcd.drawString(buffer, 160, 120, GFXFF);

            uint64_t lastTime = millis();
            while (millis() < (lastTime + 2000));
            delay(100);
          }

          buttonTimer = millis(); // zero the timer
          selectMode = true;
        }

        if (selectMode && millis() > (buttonTimer + 4000))
        {
          //
          // When selection is stable, send persistent Brightness level to all other units
          // over CAN and save it in preferences.  This delay minimizes memory wear.
          //
          selectMode = false;

          /* 		   if (myFlight.dimChannel >= 1 && myFlight.dimChannel <= 4)
          		   {
          		       myFlight.Backlight (Brightness, myFlight.dimChannel, 2); // share persistent Brightness information
          		   } */

          preferences.begin ("huVVer-app", false);
          preferences.putUInt("Brightness", Brightness);
          preferences.end();

          static uint64_t lastTime = millis();
          while (millis() < (lastTime + 500));
        }

        //
        // Hold Select button B (O) to toggle dimmer up/down direction
        //
        /*         if (SelectBtn.releasedFor(750))
                {
                  // For pressedFor() must use PrintMessage, not DrawMessage
                  myMessage = " Dim Toggle "; // Extra spaces to overwrite OTA Update message
                  myFlight.PrintMessage (myMessage, 500, TFT_WHITE, TFT_WHITE);
                  dimDirection = !dimDirection;
                } */
      }

      //
      // press Fwd Button C (>) to change selected item.
      //
      // if (FwdBtn.wasPressed() && !BackBtnWasPressed())
      // if (SelectBtn.releasedFor(2000))
      // if (btn_right.hold(2))//листать виджеты по одному
      // if (btn_right.step(2))//листать виджеты по одному
      if (button2.isPressed())//листать виджеты по одному
      {
        displayDirection = 1;
        displayMode ++;
        delay(500);

        // Reset the menu selection to top of the list
        myMenus.menuState = MENU_DEFAULT;
        myMenus.itemSelect = 1;
        myMenus.digitAction = 0;
        myMenus.digitSelect = -1;
        myMenus.loadPrefs = true;
      }

      /*       if (btn_right.step(2) && btn_left.hold(1))//листать виджеты непрерывно - пока не работает
            {
              displayDirection = 1;
              displayMode ++;
      		delay(500);

              // Reset the menu selection to top of the list
              myMenus.menuState = MENU_DEFAULT;
              myMenus.itemSelect = 1;
              myMenus.digitAction = 0;
              myMenus.digitSelect = -1;
              myMenus.loadPrefs = true;
            } */

      //
      // press Back Button D (<) to change selected item.
      //
      // if (BackBtnWasPressed() && !SelectBtn.wasPressed() && !MenuBtn.wasPressed())
      // if (MenuBtn.pressedFor(2000))
      if (btn_left.step(2))
      // if (button2.isPressed())
        // if (btn_left.hold(2))//листать виджеты по одному
      {
        displayDirection = -1;
        displayMode --;
        delay(500);

        // Reset the menu selection to top of the list
        myMenus.menuState = MENU_DEFAULT;
        myMenus.itemSelect = 1;
        myMenus.digitAction = 0;
        myMenus.digitSelect = -1;
        myMenus.loadPrefs = true;
      }

      /*       if (btn_left.step(2) && btn_right.hold(1))//листать виджеты непрерывно - пока не работает
            {
              displayDirection = -1;
              displayMode --;
      		delay(500);

              // Reset the menu selection to top of the list
              myMenus.menuState = MENU_DEFAULT;
              myMenus.itemSelect = 1;
              myMenus.digitAction = 0;
              myMenus.digitSelect = -1;
              myMenus.loadPrefs = true;
            } */

      break; // end (MENU_DEFAULT)


    case MENU_LIST:

      // if (BackBtnWasPressed() && !FwdBtn.wasPressed())
      // if (MenuBtn.pressedFor(2000) && !SelectBtn.wasReleased())
      // if (MenuBtn.wasPressed && !SelectBtn.wasReleased())
      // if (btn_left.hold(1))// > button
      // {
      //   myMenus.itemSelect --;
      //   if (DEBUG) Serial.println (myMenus.itemSelect);
      // }

      // if (FwdBtn.wasPressed() && !BackBtnWasPressed())
      // if (!MenuBtn.wasPressed() && SelectBtn.releasedFor(1000))
      // нажимая правую кнопку перемещаемся вперед(по кругу) по пунктам настроек
      // if (MenuBtn.read() && SelectBtn.wasReleased())
      if (btn_right.hold(1))// > button
      { // > button
        myMenus.itemSelect ++;
        if (DEBUG) Serial.println (myMenus.itemSelect);
      }

      // возврат на самый верх к виджетам
      // if (MenuBtn.pressedFor(4000) && !SelectBtn.wasReleased())
      // if (MenuBtn.wasPressed() && !SelectBtn.wasReleased())
      // if (btn_left.hold(1))//выход из меню параметров
      if(button1.isPressed())
      { // [] button
        myMenus.menuState = MENU_DEFAULT;
        if (DEBUG) Serial.println ("Default");
      }

      // if (SelectBtn.releasedFor(2000) && !MenuBtn.wasPressed())
      if (btn_right.hold(2))
      { // O button
        myMenus.menuState = MENU_ACTION;
        myMenus.digitSelect = 1;     //enable digit editing
        if (DEBUG) Serial.println ("Data Entry");
      }
      break; // end (MENU_LIST)

    case MENU_ACTION:

      // if (BackBtnWasPressed() && !FwdBtn.wasPressed())
      // if (MenuBtn.pressedFor(2000) && SelectBtn.read())
      if (btn_left.hold(1)) // < button
      {
        myMenus.digitAction =  -1;
        if (DEBUG) Serial.println ("<");
      }

      // if (FwdBtn.wasPressed() && !BackBtnWasPressed())
      // if (!MenuBtn.wasPressed() && SelectBtn.releasedFor(1000))
      // if (MenuBtn.wasPressed() && SelectBtn.read())
      if (btn_right.hold(1)) // > button
      { // > button
        myMenus.digitAction =  +1;
        if (DEBUG) Serial.println (">");
      }

      // if (MenuBtn.pressedFor(4000) && !SelectBtn.wasReleased())
      if (btn_right.hold(3))
      { // [] button

        //
        // Trigger the save data function
        //
        myMenus.saveAction = true;

        myMenus.menuState = MENU_LIST;
        myMenus.digitAction = 0;
        myMenus.digitSelect = -1;
        if (DEBUG) Serial.println ("Data Saved Temporarily");
      }

      // if (SelectBtn.wasReleased() && !MenuBtn.wasPressed())
      if (btn_right.hold(2))
      { // O button
        myMenus.digitSelect ++;
        myMenus.digitAction = 0;
        if (DEBUG) Serial.println ("digit " + String (myMenus.digitSelect));
      }
      break; // end (MENU_ACTION)

    //должно быть всплывающее меню из 4-х пунктов
    case MENU_SETUP:

      if(button2.isPressed())
      // if (btn_right.hold(1)) // > button
      { // > button

        if (DEBUG) Serial.println ("Change button");
      }

      // Вход в четырехкнопочное меню - удерживаем такую то кнопку
      // if (!MenuBtn.read() && SelectBtn.releasedFor(2000))
      // if (btn_left.hold(1))
      if(button1.isPressed())
      {
        myMenus.menuState = MENU_DEFAULT;
        // myMenus.loadPrefs = true;

        preferences.begin ("huVVer-app", false); // restore brightness during setup
        Brightness = preferences.getUInt("Brightness", 1023);
        myFlight.Backlight(Brightness);
        preferences.end();

        if (DEBUG) Serial.println ("Return to Main Menu");
      }
      break; // end (MENU_ACTION)

    default:
      if (DEBUG) Serial.println ("Illegal Menu");
      break;


  }

  //
  // Main serial processing routing, reads all incoming serial data and processes sentences (aka packets) when detected.
  //
  if (operationMode == WIFIOFF || operationMode == WIFITX || operationMode == WIFIOFFDEMO || operationMode == WIFITXDEMO)
  {
    switch (SerialPort)
    {
      case 2:
        while (Serial2.available())
        {
          myEFISdata.serPacketFrame(2);
          if (myEFISdata.packetValid) break;
        }
        break;
      case -2:
        while (Serial2.available())
        {
          myEFISdata.serPacketFrame(2);
          if (myEFISdata.packetValid) break;
        }
        break;
      case 0:
        while (Serial.available() )
        {
          myEFISdata.serPacketFrame(0);		// process data
          if (myEFISdata.packetValid) break;	// until packet received
        }
        break;
      case 1:
        while (Serial1.available())
        {
          myEFISdata.serPacketFrame(1);
          if (myEFISdata.packetValid) break;
        }
        break;
      case -1:
        while (Serial1.available())
        {
          myEFISdata.serPacketFrame(1);
          if (myEFISdata.packetValid) break;
        }
        break;
    }
  }

  //
  // Get packets from WiFi (if provisioned).
  //
  else if (operationMode == WIFIRX || operationMode == WIFIRXDEMO)
  {
    int packetSize = udp.parsePacket();
    if (packetSize > 0)
    {
      if (DEBUG)
      {
        Serial.printf("Received packet of size %d ", packetSize);
        IPAddress remoteIp = udp.remoteIP();
        Serial.printf("From Port %d ", udp.remotePort());
      }

      // read the packet into packetChars
      int len = udp.read(myEFISdata.packetChars, NUMCHARS);
      if (len > 0)
      {
        myEFISdata.packetChars[len] = 0;
      }
      if (DEBUG) Serial.printf ("Contents: %s", myEFISdata.packetChars);

      //
      // Forward packet data to selected serial port
      //
      if (SerialPort == 1 || SerialPort == -1) Serial1.println(myEFISdata.packetChars);
      if (SerialPort == 2 || SerialPort == -2) Serial2.println(myEFISdata.packetChars);

      // Extract the data fields.
      myEFISdata.udpPacketFrame(myEFISdata.packetChars, len);
    }
  }

  // if (myEFISdata.packetValid)  // a sentence has been detected.
  if (true)  // a sentence has been detected.
  {
    frameCount = FRAMES;  // indicates serial link activity.

    //
    // Send over WiFi (if provisioned).
    //
    if (operationMode == WIFITX || operationMode == WIFITXDEMO)
    {
      //
      // send udp
      //
      udp.beginPacket(broadcastIp, port); // remote IP and port
      udp.print(myEFISdata.packetChars);
      udp.endPacket();
      if (DEBUG) Serial.printf ("Destination Port: %d\n", port);
      if (DEBUG) Serial.println (broadcastIp);
    }
  }

  myEFISdata.packetValid = false; // release framed packet

  //
  // check for dimmer value received on CAN bus
  //
//  if (CAN.parsePacket())
//  {
//    uint16_t checkBright = myFlight.receiveCANdim();
//    if (checkBright > 0) Brightness = checkBright;
//  }

  //
  // Set loop update time and alarm flash rate
  //
  if (millis() > (loopTime + LOOPTIME))
  { //in milliseconds
    loopTime = millis();
    gdraw.fillSprite (TFT_BLACK);

    // Disable timeout if in demonstration mode, system menu, readme menu, or submenus.
    if (operationMode == WIFIOFFDEMO || operationMode == WIFIRXDEMO || operationMode == WIFITXDEMO
        || !myMenus.menuState == MENU_DEFAULT) frameCount = FRAMES;

    //
    // Main instrument display selection
    //
    if (displayMode > READMEPAGE) displayMode = SYSTEMPAGE;

    if (displayMode < SYSTEMPAGE) displayMode = READMEPAGE;

    preferences.begin ("getSetup", false);
    int32_t restoreAll = preferences.getInt(RESTOREALL, 0); // check bit to clear to factory new
    preferences.end();

    if (displayDirection > 0)
    {
      switch (displayMode)
      {
        case SYSTEMPAGE:
          myFlight.SystemSetup();

          if (restoreAll > 0)
          {
            myFlight.RestoreAll();
            if (DEBUG) Serial.println ("All Preferences restored, restarting");
            myFlight.Backlight(0);
            ESP.restart();
          }
          break;

        case 1:
          if (myFlight.asiEnable)
          {
            myFlight.ASIDisplay (AirSpeed, TASpeed, ASBug);
            break;
          }
          else displayMode ++;

        case 2:
          if (myFlight.altEnable)
          {
            myFlight.ALTDisplay (Altitude, PressAlt, DensAlt,
                                 Baro, AltBug, AirSpeed, VertSpeed, VSBug);
            break;
          }
          else displayMode ++;

        case 3:
          if (myFlight.hdgEnable)
          {
            myFlight.HDGDisplay (Heading, HdgBug, Course,
                                 WindDir, WindSpeed);
            break;
          }
          else displayMode ++;

        case 4:
          if (myFlight.aiEnable)
          {
            myFlight.AiDisplay (Pitch, Roll, Slip, AirSpeed,
                                Altitude, PressAlt, DensAlt, Baro,
                                Heading, VertAccel, AOA, FlightPath,
                                DriftAngle);
            break;
          }
          else displayMode ++;

        case 5:
          if (myFlight.aoaEnable)
          {
            myFlight.AOADisplay (AOA, Slip, VertAccel, flashFlag,
                                 AirSpeed, TASpeed, Heading, Altitude,
                                 PressAlt, DensAlt, VertSpeed, Baro);
            break;
          }
          else displayMode ++;

        case 6:
          if (myFlight.energyEnable)
          {
            myFlight.EnergyDisplay (AOA, OnSpeedAOA, SmoothedAOA,
                                    Slip, VertAccel, AirSpeed, FlapPos,
                                    flashFlag, OSTonesOnAOA, OSFastAOA, OSSlowAOA,
                                    OSStallWarnAOA, gOnsetRate);
            break;
          }
          else displayMode ++;

        case 7:
          if (myFlight.powerEnable)
          {
            //
            // compensates for fuel return flow to tank
            //
            int16_t FuelFlow = 0, fuel1 = 0, fuel2 = 0;
            if (FuelF1 >= 0) fuel1 = FuelF1;
            if (FuelF2 >= 0) fuel2 = FuelF2;
            FuelFlow = fuel1 - fuel2;
            if (FuelFlow <  0) FuelFlow = 0;

            myFlight.PowerDisplay(RpmL,  Map,  FuelFlow,  FuelP, FuelRem,  PercentPwr,  LeanState, GroundTrack, GroundSpeed);
            break;
          }
          else displayMode ++;

        case 8: // EGTCHT
          if (myFlight.egtChtEnable)
          {
            int16_t FuelFlow = 0, fuel1 = 0, fuel2 = 0;
            if (FuelF1 >= 0) fuel1 = FuelF1;
            if (FuelF2 >= 0) fuel2 = FuelF2;
            FuelFlow = fuel1 - fuel2;
            if (FuelFlow <  0) FuelFlow = 0;

            myFlight.EgtChtDisplay(TCpl, 14, FuelFlow, FuelRem, PercentPwr,  LeanState, GroundSpeed);
            break;
          }
          else displayMode ++;

        case 9: // Fuel, Oil, Volts, Amps
          if (myFlight.fuelOilEnable)
          {
            myFlight.FuelOilDisplay	(FuelL, FuelR,
                                     FuelP,  OilP,  OilT,
                                     Volts1,  Amps);
            break;
          }
          else displayMode ++;

        case 10: // Flow, Oil, Volts, Amps
          if (myFlight.flowOilEnable)
          {
            //
            // compensates for fuel return flow to tank
            //
            int16_t FuelFlow = 0, fuel1 = 0, fuel2 = 0;
            if (FuelF1 >= 0) fuel1 = FuelF1;
            if (FuelF2 >= 0) fuel2 = FuelF2;
            FuelFlow = fuel1 - fuel2;
            if (FuelFlow <  0) FuelFlow = 0;

            myFlight.FlowOilDisplay	(FuelFlow, FuelRem, FuelP,
                                     OilP,  OilT, Volts1,  Amps);
            break;
          }
          else displayMode ++;

        case 11:
          if (myFlight.adahrsRaw)
          {
            myFlight.ADAHRSRaw();
            break;
          }
          else displayMode ++;

        case 12:
          if (myFlight.systemRaw)
          {
            myFlight.SYSTEMRaw();
            break;
          }
          else displayMode ++;

        case 13:
          if (myFlight.emsRaw1)
          {
            myFlight.EMSRaw1();
            break;
          }
          else displayMode ++;

        case 14:
          if (myFlight.emsRaw2)
          {
            myFlight.EMSRaw2();
            break;
          }
          else displayMode ++;

        case 15:
          if (myFlight.emsRaw3)
          {
            myFlight.EMSRaw3();
            break;
          }
          else displayMode ++;

        case READMEPAGE: // Notes
          //if (myFlight.readMe)
          //{
          myFlight.ReadMe();
          break;
        //}
        //else displayMode ++;

        default:
          break;
      }
    }

    else
    {
      switch (displayMode)
      {
        case READMEPAGE: // Notes
          //if (myFlight.readMe)
          //{
          myFlight.ReadMe();
          break;
        //}
        //else displayMode --;

        case 15:
          if (myFlight.emsRaw3)
          {
            myFlight.EMSRaw3();
            break;
          }
          else displayMode --;

        case 14:
          if (myFlight.emsRaw2)
          {
            myFlight.EMSRaw2();
            break;
          }
          else displayMode --;

        case 13:
          if (myFlight.emsRaw1)
          {
            myFlight.EMSRaw1();
            break;
          }
          else displayMode --;

        case 12:
          if (myFlight.systemRaw)
          {
            myFlight.SYSTEMRaw();
            break;
          }
          else displayMode --;

        case 11:
          if (myFlight.adahrsRaw)
          {
            myFlight.ADAHRSRaw();
            break;
          }
          else displayMode --;

        case 10: // Flow, Oil, Volts, Amps
          if (myFlight.flowOilEnable)
          {
            //
            // compensates for fuel return flow to tank
            //
            int16_t FuelFlow = 0, fuel1 = 0, fuel2 = 0;
            if (FuelF1 >= 0) fuel1 = FuelF1;
            if (FuelF2 >= 0) fuel2 = FuelF2;
            FuelFlow = fuel1 - fuel2;
            if (FuelFlow <  0) FuelFlow = 0;

            myFlight.FlowOilDisplay	(FuelFlow, FuelRem, FuelP,
                                     OilP,  OilT, Volts1,  Amps);
            break;
          }
          else displayMode --;

        case 9: // Fuel, Oil, Volts, Amps
          if (myFlight.fuelOilEnable)
          {
            myFlight.FuelOilDisplay	(FuelL, FuelR,
                                     FuelP,  OilP,  OilT,
                                     Volts1,  Amps);
            break;
          }
          else displayMode --;

        case 8: // EGTCHT
          if (myFlight.egtChtEnable)
          {
            int16_t FuelFlow = 0, fuel1 = 0, fuel2 = 0;
            if (FuelF1 >= 0) fuel1 = FuelF1;
            if (FuelF2 >= 0) fuel2 = FuelF2;
            FuelFlow = fuel1 - fuel2;
            if (FuelFlow <  0) FuelFlow = 0;

            myFlight.EgtChtDisplay(TCpl, 14, FuelFlow, FuelRem, PercentPwr,  LeanState, GroundSpeed);
            break;
          }
          else displayMode --;

        case 7: // Power
          if (myFlight.powerEnable)
          {
            //
            // compensates for fuel return flow to tank
            //
            int16_t FuelFlow = 0, fuel1 = 0, fuel2 = 0;
            if (FuelF1 >= 0) fuel1 = FuelF1;
            if (FuelF2 >= 0) fuel2 = FuelF2;
            FuelFlow = fuel1 - fuel2;
            if (FuelFlow <  0) FuelFlow = 0;

            myFlight.PowerDisplay(RpmL,  Map,  FuelFlow,  FuelP, FuelRem,  PercentPwr,  LeanState, GroundTrack, GroundSpeed);
            break;
          }
          else displayMode --;

        case 6:
          if (myFlight.energyEnable)
          {
            myFlight.EnergyDisplay (AOA, OnSpeedAOA, SmoothedAOA,
                                    Slip, VertAccel, AirSpeed, FlapPos,
                                    flashFlag, OSTonesOnAOA, OSFastAOA, OSSlowAOA,
                                    OSStallWarnAOA, gOnsetRate);
            break;
          }
          else displayMode --;

        case 5:
          if (myFlight.aoaEnable)
          {
            myFlight.AOADisplay (AOA, Slip, VertAccel, flashFlag,
                                 AirSpeed, TASpeed, Heading,
                                 Altitude, PressAlt,
                                 DensAlt, VertSpeed, Baro);
            break;
          }
          else displayMode --;

        case 4:
          if (myFlight.aiEnable)
          {
            myFlight.AiDisplay (Pitch, Roll, Slip, AirSpeed,
                                Altitude, PressAlt, DensAlt, Baro,
                                Heading, VertAccel, AOA, FlightPath,
                                DriftAngle);
            break;
          }
          else displayMode --;

        case 3:
          if (myFlight.hdgEnable)
          {
            myFlight.HDGDisplay (Heading, HdgBug, Course,
                                 WindDir, WindSpeed);
            break;
          }
          else displayMode --;

        case 2:
          if (myFlight.altEnable)
          {
            myFlight.ALTDisplay (Altitude, PressAlt, DensAlt,
                                 Baro, AltBug, AirSpeed, VertSpeed, VSBug);
            break;
          }
          else displayMode --;

        case 1:
          if (myFlight.asiEnable)
          {
            myFlight.ASIDisplay (AirSpeed, TASpeed, ASBug);
            break;
          }
          else displayMode --;

        case SYSTEMPAGE:
          myFlight.SystemSetup();

          if (restoreAll > 0)
          {
            myFlight.RestoreAll();
            if (DEBUG) Serial.println ("All Preferences cleared, restarting");
            myFlight.Backlight(0);
            ESP.restart();
          }
          break;

        default:
          break;
      }
    }

    //
    // Look for serial link failure
    //
    frameCount --;

    if (frameCount <= 0 && displayMode != SYSTEMPAGE && displayMode != READMEPAGE)
    {
      // Draw red X across display
      frameCount = 0;
      myGauges.drawLine (0, 0, SCREEN_X, SCREEN_Y, TFT_RED, 6, NONE, gdraw.alphaBlend (100, TFT_WHITE, TFT_RED), 2, NONE);
      myGauges.drawLine (0, SCREEN_Y, SCREEN_X, 0, TFT_RED, 6, NONE, gdraw.alphaBlend (100, TFT_WHITE, TFT_RED), 2, NONE);
    }

    //
    // flashFlag toggles every loop to allow for alarm indication.
    // Changing the LOOPTIME constant will also change the flash rate.
    //
    flashCount++;
    if (flashCount >= FLASHCOUNT)
    {
      flashCount = 0;
      flashFlag = !flashFlag;
    }

    //
    // Deferred message overlay printing.  msgTime is the number of display loops.
    //
    if (msgTime) myFlight.DrawMessage (myMessage, TFT_WHITE, TFT_WHITE);
    if (msgTime > 0) msgTime--;

    //
    // Mode indication, copyrights, and error counts when in DEMO mode
    //
    if ((operationMode == WIFIOFFDEMO || operationMode == WIFIRXDEMO ||
         operationMode == WIFITXDEMO) && myMenus.menuState == MENU_DEFAULT)
    {
      gdraw.setFreeFont(TT1);
      gdraw.setTextColor (TFT_WHITE, TFT_BLACK);
      gdraw.setTextDatum (BC_DATUM);
      // gdraw.setTextPadding (gdraw.textWidth("DEMO MODE, NOT FOR FLIGHT") + 4);
      gdraw.drawString ("DEMO MODE, NOT FOR FLIGHT", SCREEN_X / 2, SCREEN_Y);
    }

    //
    // Push updated display to screen
    //
    // gdrawMenu.pushSprite (0, 0);
    gdraw.pushSprite (0, 0);

    // All instruments should execute in an average of about 60 milliseconds
    // or less to minimize packet loss cause by eventual buffer overflow.
    if (DRAWTIME)
    {
      Serial.println ((micros() - time1) / 1000);
      Serial.print ("Errs: "); Serial.println (myEFISdata.checkSumErrs);
    }
  } // end if (millis() > (loopTime + LOOPTIME);
  // btn_left.tick();
  // btn_right.tick();
} // end of loop
esp:VIN
esp:GND.2
esp:D13
esp:D12
esp:D14
esp:D27
esp:D26
esp:D25
esp:D33
esp:D32
esp:D35
esp:D34
esp:VN
esp:VP
esp:EN
esp:3V3
esp:GND.1
esp:D15
esp:D2
esp:D4
esp:RX2
esp:TX2
esp:D5
esp:D18
esp:D19
esp:D21
esp:RX0
esp:TX0
esp:D22
esp:D23
lcd1:VCC
lcd1:GND
lcd1:CS
lcd1:RST
lcd1:D/C
lcd1:MOSI
lcd1:SCK
lcd1:LED
lcd1:MISO
lcd1:SCL
lcd1:SDA
btn1:1.l
btn1:2.l
btn1:1.r
btn1:2.r
r1:1
r1:2
btn2:1.l
btn2:2.l
btn2:1.r
btn2:2.r
r2:1
r2:2
r3:1
r3:2
led1:A
led1:C