#include <ESP8266WiFi.h> //библиотека для взаимодействия платы с wi-fi сетями
#include <ESP8266WebServer.h> //библиотека для создания веб-сервера
#include <ESP8266HTTPClient.h> 
#include <Wire.h> //библиотека для работы с шиной I2C
#include <LiquidCrystal_I2C.h> //библиотека для работы с LCD 20x4
#include <DS3231.h> //библиотека для работы с часами реального времени DS3231
#include <Adafruit_BMP280.h> //библиотека для работы с датчиками bmp280
#include <DNSServer.h>
#include <WiFiManager.h>         
#include <ESP8266SSDP.h>
#include <aREST.h>
#include <FS.h>
#include <ArduinoJson.h> //библиотека для обработки данных
#include <ESP8266WiFiMulti.h> //библиотека для работы с множеством точек досутпа
using namespace std;
#define PERIOD (5*60*1000) // 5 минут в миллисекундах


ESP8266WiFiMulti wifiMulti;
String myAP;     //режим точки доступа
String mySSID;   //флаг использования собственной точки доступа WiFi
IPAddress myIP;  //хранит IP адресс  
unsigned long oldmillis=PERIOD+PERIOD;    //затравочное ("неправильное") значение

const int wf=7; //количество сканирируемых сетей
const int wf_configs=20; //количество сохраняемых элементов в памяти
int count_wifi=20;
String ssid[wf_configs]={}; //массив имен сетей
String password[wf_configs]={}; //массив паролей сетей

String ssid_scan[wf]={};

int rssi[wf]={}; //может принимать значения от 0 до -100 дБм (чем выше значение RSSI (ближе к 0), тем сигнал лучше (мощнее))

size_t indexAppendSsid = 0;
const char* _ssid[wf_configs] ={};
const char* _password[wf_configs] ={};


ESP8266WebServer HTTP(80);   //Web интерфейс для устройства
HTTPClient http;
WiFiServer SERVERaREST(8080);   //aREST и сервер для него
aREST rest = aREST();
File fsUploadFile;

#define BMP280_ADDRESS (0x76) //адрес для подключения датчиков bmp280 через I2C
#define D1 5
#define D2 4
 
LiquidCrystal_I2C lcd(0x27,20,4); //указываем I2C адрес, также параметры экрана (в случае LCD 2004 - 4 строки по 20 символов в каждой)
DS3231 cl;
RTCDateTime dt;
Adafruit_BMP280 bmp;

//пины мультиплексора 
int S0 = 15;
int S1 = 13;
int S2 = 12;
int S3 = 14;
int SIG = 4;

String jsonConfig = "{}";                  //переменная, хранящая файл конфигурации

int old_time=0; //запоминает минуту (для вывода раз в минуту)
double pressure=0;  //сумма значений атмосферного давления со всех датчиков 
double temp=0; //сумма значений температуры 
double av_val_p=0; //среднее значение атмосферного давления
double av_val_p_mm=0; //среднее значение атмосферного давления в мм.рт.ст
double av_val_t=0; //среднее значение температуры 
double mmHg=133.3; //величина для перевода паскалей в мм.рт.ст

double av_val_p_mediana=0; //среднее значение атмосферного давления (медиана)
double av_val_m=0; //среднее  значение (медиана)
double av_val_t_mediana=0;

int hr=0; //часы
int mnt=0; //минуты
int scnd=0; //секунды
int d=0; //дни
int mnth=0; //месяцы
int yr=0; //года

const int n=9; // количество датчиков
int mux[n]={0b0000, 0b0001, 0b0010, 0b0011, 0b0100, 0b0101, 0b0110, 0b0111, 0b1000}; //массив входов мультиплексора 
int muxCh[n]={0, 1, 2, 3, 4, 5, 6, 7, 8}; 
double bmp_pr[n]; // массив значений атмосерного давления с каждого датчика 
double bmp_temp[n]; // массив значений температуры 

String ssidAP = "Barometr";     // SSID точки доступа
String passwordAP = "";     // Пароль точки доступа


void setup() 
{
  Serial.begin(115200); // инициализируем последовательный порт (для отладочных целей)
  lcd.begin(D2,D1);   //инициализируем LCD дисплей
  lcd.backlight(); //включаем подсветку дисплея 
  
  //настраиваем HTTP интерфейс
  HTTP_init();
   //запускаем SSDP сервис
  SSDP_init();

  // регистрируем в aRest функции irControl
  rest.function("irc", irControl);

  // включаем aREST и сервер к нему
  AREST_init();

  //загрузка конфигурации
  LoadConfig(); 
  delay(1000);

  for(size_t i = 0; i < wf_configs; i++) {
      if (ssid[i] != "") 
      {
        count_wifi=count_wifi-1;
      }
  }

  WiFi.persistent(false);
  WiFi.mode(WIFI_STA);  //переключение моудля в режим станции
  
  for (int i=0; i<wf_configs;i++)
  {
     wifiMulti.addAP(_ssid[i],_password[i]);
  }

  ReinitWiFi(0);
  
  delay(10);
  cl.begin();
  pinMode (S0, OUTPUT);
  pinMode (S1, OUTPUT);
  pinMode (S2, OUTPUT);
  pinMode (S3, OUTPUT);
  //cl.setDateTime(__DATE__,__TIME__); //начальная установка времени и даты (время и дата компьютера устанавливаются в датчик) нужна только при первой прошивке
}

//для красивого вывода на lcd
String print3(int m)
{ 
  if (m >= 0 && m < 10) 
  { return ("0"+String(m));
  }
  else {return (String(m));}
}


//для красивого вывода на lcd
void print2(int m)
{ 
  if (m >= 0 && m < 10) 
  { 
    lcd.print("0");
    lcd.print(m);
  }
  else {lcd.print(m);}
}

void loop() 
{
  hr=dt.hour;
  mnt=dt.minute;
  scnd=dt.second;
  d=dt.day;
  mnth=dt.month;
  yr=dt.year;
  dt=cl.getDateTime();

  lcd.clear();
  lcd.setCursor(0,0); lcd.print("Pressure:");
  lcd.setCursor(7,1); lcd.print("mm");
  lcd.setCursor(0,2); lcd.print("Temp:");
  lcd.setCursor(5,3); lcd.print("\xDF");
  lcd.setCursor(6,3); lcd.print("C");
  lcd.setCursor(10,0);  lcd.print("Time:");
  lcd.setCursor(12,1);  lcd.print(":");
 //lcd.setCursor(15,1);  lcd.print(":"); //вывод секунд
  lcd.setCursor(10,2);  lcd.print("Date:");
  lcd.setCursor(12,3);  lcd.print(".");
  lcd.setCursor(15,3);  lcd.print(".");
  lcd.setCursor(10,1);  print2(hr);
  lcd.setCursor(13,1);  print2(mnt); 
 //lcd.setCursor(16,1);  print2(scnd);  //выводд секунд 
  lcd.setCursor(10,3);  print2(d);
  lcd.setCursor(13,3);  print2(mnth);
  lcd.setCursor(16,3);  lcd.print(yr);
  
  //вывод среднего значения на LCD-экран
//lcd.setCursor(0,1); lcd.print(av_val_p_mm); 
//lcd.setCursor(0,3); lcd.print(av_val_t);

  //вывод медианы  
  lcd.setCursor(0,1); lcd.print(av_val_p_mediana);
  lcd.setCursor(0,3); lcd.print(av_val_t_mediana);
  delay(1000);

  pressure=0; // атмосферное давление
  temp=0; // температура
  av_val_p=0; // среднее значение атмосферного давления
  av_val_p_mm=0; // среднее значение атмосферного давления в мм.рт.ст
  av_val_t=0; // среднее значение температуры 
  av_val_m =0;
  av_val_p_mediana=0; // среднее значение атмосферного давления (медиана)
  av_val_t_mediana=0; // среднее значение температуры (медиана)
  
  for (int i=0; i<n;i++)
  {
    digitalWrite(S0,bitRead(mux[i],0));
    digitalWrite(S1,bitRead(mux[i],1));
    digitalWrite(S2,bitRead(mux[i],2));
    digitalWrite(S3,bitRead(mux[i],3));
    
    if (!bmp.begin(BMP280_ADDRESS)) 
      {
        lcd.setCursor(0,0);
        lcd.print(F("Error     ")); //если датчик не найден - выводим ошибку и номер входа мультиплексора, к которому подключен неисправный датчик
        lcd.setCursor(0,1); lcd.print("C");
        lcd.setCursor(1,1); lcd.print(muxCh[i]);
        lcd.setCursor(3,1); lcd.print("       ");
        while (1);
      }
    bmp.setSampling(Adafruit_BMP280::MODE_NORMAL,   //режим работы датчика bmp280
                  Adafruit_BMP280::SAMPLING_X2,   //точность изм. температуры  
                  Adafruit_BMP280::SAMPLING_X16,  //точность изм. давления 
                  Adafruit_BMP280::FILTER_X16,    //уровень фильтрации
                  Adafruit_BMP280::STANDBY_MS_1); //период просыпания, мСек
      
    pressure=pressure+bmp.readPressure();  //сумма значений атмосферного давления 
    temp=temp+bmp.readTemperature();       //сумма значений температуры
    bmp_pr[i]=bmp.readPressure()/mmHg;     //массивы значений с каждого датчика 
    bmp_temp[i]=bmp.readTemperature();
  }
  //сортировка
  sort_1(bmp_pr);
  sort_1(bmp_temp);
  
  av_val_m=bmp_pr[4];
  av_val_p_mediana = av_val_m;
  av_val_p=pressure/n;
  av_val_p_mm=(av_val_p/mmHg);
  av_val_t=temp/n;
  av_val_t_mediana=bmp_temp[4];

  
  //передача пакета данных json раз в минуту в базу данных вуза
  //вывод на lcd-экран и в монитор порта название точки доступа, к которой удалось подключиться и ip-адреса раз в минуту
  if (mnt!=old_time){sendJsonToDB(); lcd_v(); old_time=mnt;}
  else {old_time=mnt;}

  //apsI0gqvFn
  //проверка/переподключение к сети wi-fi
  unsigned long mls;
  unsigned long mls_t;
  mls=millis();
  mls_t=mls%PERIOD;
  if(mls_t<oldmillis)
  {
   ReinitWiFi(1);
   oldmillis=mls_t;
  }

  HTTP.handleClient();
  delay(1);
  WiFiClient client = SERVERaREST.available();
  if (!client) 
  {
    return;
  }
  while (!client.available()) 
  {
    delay(1);
  }
  rest.handle(client);
}
