Отказоустойчивый доступ к сети с несколькими ip шлюзами (на ESP8266)

ilya7zz

✩✩✩✩✩✩✩
16 Мар 2021
19
1
Суздаль
www.drive2.ru
Всем здравия!
Для личного пользования занимаюсь изучением и настройкой различных измерялок и моргалок на ESP8266 (жду ESP32 c Ali).
Возникло желание осуществить отказоустойчивый доступ к сети сервера Blynk.
Домашняя ЛВС сеть содержит 3 шлюза:
10.0.1.1 — Основной домашний провайдер
10.0.2.1 — Альтернативный доступ (открытая сеть по соседству)
10.0.3.1 — GSM роутер (резервный канал)
Задача:
Реализовать возможность использовать шлюз для доступа к интернету (серверу) самим устройством, с заданными метриками (приоритетами) и проверкой работоспособности (наличия связи с сервером или/и доступности ping dns).
В программировании не силён, ничего похожего за несколько дней найти не удалось.

На примере датчика температуры:
/* ESP & Blynk Котельная v001*/
#include <ESP8266WiFi.h>
#include <BlynkSimpleEsp8266.h>
#define BLYNK_PRINT Serial    

/* Учетные данные Blynk */
char auth[] = "a2xho_gJ-mfvcLdss8FYjPc0X1XE0bJ"; // Blynk Котельная

/* Учетные данные WiFi */
char ssid[] = "Wi-Fi_Home";
char pass[] = "Password1";
/* Определяем конфигурацию сети *
char myDns
char gateway
byte mac[] = {0x3C, 0x61, 0x05, 0xC0, 0xA5, 0x01}; // MAC-адрес Котельная 
byte ip[] = {10, 0, 3, 11}; // IP-адрес
byte myDns1[] = {77, 88, 8, 8}; // адрес DNS-сервера Yandex
byte myDns2[] = {8, 8, 8, 8}; // адрес DNS-сервера Google
byte gateway1[] = {10, 0, 1, 1}; // адрес сетевого шлюза metric 1
byte gateway2[] = {10, 0, 2, 1}; // адрес сетевого шлюза metric 2
byte gateway3[] = {10, 0, 3, 1}; // адрес сетевого шлюза metric 3
byte subnet[] = {255, 255, 0, 0}; // маска подсети
/* Задать метрику и скрипт проверки работоспособности по таймеру ip, mydns */

/* TIMER */
#include <SimpleTimer.h>
SimpleTimer timer;

/* DS18B20 Temperature Sensor */
#include <OneWire.h>
#include<DallasTemperature.h> 
#define ONE_WIRE_BUS 0 // DS18B20 подключаем к GPIO0 на плате

OneWire oneWire(ONE_WIRE_BUS); // Настраиваем экземпляр oneWire для связи с любыми устройствами OneWire
DallasTemperature DS18B20(&oneWire); // Передаем нашу ссылку oneWire в Dallas Temperature.
float tmp_0; //Хранит числа с плавающей точкой (десятичные дроби). Точность: 6-7 знаков температура до #.###

void setup() 
{
  Serial.begin(115200); // запускаем последовательный порт
  Blynk.begin(auth, ssid, pass);
//Blynk.begin(mac, ip, myDns, gateway, subnet); // инициализация контроллера?
  DS18B20.begin(); // Запускаем библиотеку (инициализация датчика)
  timer.setInterval(1000L, getSendData);
}

void loop() 
{
  timer.run(); // Initiates SimpleTimer
  Blynk.run();
}

/***************************************************
 * Отправка данных сенсоров в Blynk
 **************************************************/
void getSendData()
{
  DS18B20.requestTemperatures(); // Cчитываем температуру с датчиков, на это требуется 750мс
  tmp_0 = DS18B20.getTempCByIndex(0); // Показания для датчика в цельсиях
  Serial.print("IP: ");
  Serial.println(ip);
//  Serial.print("Шлюз: ");
//  Serial.println(gateway);
//  Serial.print("DNS: ");
//  Serial.println(myDns);

  Serial.print("Температура: ");
  Serial.print(tmp_0);
  Serial.println(" °C");
  Blynk.virtualWrite(10, tmp_0); //вывод данных на виртуальный пин V10
}
Далее по моим представлениями нужно прописать проверку доступности ip адресов и в соответсвии с ней, выбрать адрес с большим приоритетом и назначить его в переменную gateway
Затем, повторить тоже самое с dns сервером.

Прошу помощи написать код для этих тестов, буду проверять все идеи.
 

ilya7zz

✩✩✩✩✩✩✩
16 Мар 2021
19
1
Суздаль
www.drive2.ru
Немного разобравшись, вернулся к этому вопросу и вот что получилось.
Разумеется много ляпов и мест требующих сокращения, поэтому буду рад любым замечаниям, предложениям и поправкам :)

Версия 0:
/*     Выбор рабочего шлюза и реконнект.     */
/* Условие с приоритетом:                    */
/* Использовать GW 2       (Основной - Дом)  */
/* Если недоступен, то GW 0 (Резерв1 - Free) */
/* Если недоступен, то GW 1 (Резерв2 - 3G)   */
/* * * * * * * * * * * * * * * * * * * * * * */
#define BLYNK_PRINT Serial //Отображает "Blynk Connecting to 139.59.206.133:80"
#include <BlynkSimpleEsp8266.h>
#include <ESP8266WiFi.h>
#include <SoftwareSerial.h>
#include <SimpleTimer.h>
SimpleTimer timer;
#include <Pinger.h>
extern "C"
{
  #include <lwip/icmp.h> // needed for icmp packet definitions
}
Pinger pinger;

//WiFi data
char auth[] = "key";
char ssid[] = "ssid";
char pass[] = "pass";
byte mac[] = { 0x48, 0x3f, 0xda, 0x4a, 0x3d, 0x3c };
IPAddress device_ip  (10, 0, 8, 11);
IPAddress dns1_ip     (77, 88, 8, 8);
IPAddress dns2_ip     (8, 8, 8, 8);
IPAddress gateway0_ip (10, 0, 0, 1);
IPAddress gateway1_ip (10, 0, 1, 1);
IPAddress gateway2_ip (10, 0, 2, 1);
IPAddress subnet_mask(255, 255, 0, 0);

#define vPIN_TIMER     V20 // Таймер
#define vPIN_PINGER    V22 // Средний пинг
#define vPIN_INETSTAT  V25 // Interet status
#define LED 2  // gpio2 internal led

bool inetstat = 1, fl0, fl1, fl2, fl3, fl4;
uint32_t count_time = 0;
uint32_t period = 1000; // ругается на int (ul & l)
uint32_t timer0;
String GatewayOn;

void setup() {
  Serial.begin(115200); delay(25); Serial.println();
  pinMode(LED, OUTPUT);
  wifiConnDef();
  timer.setInterval(10000L, pingerRUN);
  timer.setInterval(1000L, Reconnect);
  PingerSetup();
}

void wifiConnDef(){
  WiFi.config(device_ip, gateway2_ip, subnet_mask);
  WiFi.begin(ssid, pass);
  Blynk.config(auth, "139.59.206.133", 80);
  GatewayOn = "10.0.2.1";
  fl4 = 1;
  Serial.println("\n = Connecting Gateway 2 - Default =");
}

void wifiConnect0(){
  WiFi.config(device_ip, gateway0_ip, subnet_mask);
  Blynk.config(auth, "139.59.206.133", 80);
  GatewayOn = "10.0.0.1";
  fl4 = 1;
}

void wifiConnect1(){
  WiFi.config(device_ip, gateway1_ip, subnet_mask);
  Blynk.config(auth, "139.59.206.133", 80);
  GatewayOn = "10.0.1.1";
  fl4 = 1;
  }

void wifiConnect2(){
  WiFi.config(device_ip, gateway2_ip, subnet_mask);
  Blynk.config(auth, "139.59.206.133", 80);
  GatewayOn = "10.0.2.1";
  fl4 = 1;
  }

void Reconnect(){
  if (inetstat != 0) {fl0 = 1; fl1 = 0; fl2 = 0; fl3 = 0;}
  if (inetstat == 0 && fl0 == 1) {fl0 = 0; fl1 = 1; timer0 = millis(); Serial.println("\n !!! Start Reconnect !!!");}

  if (inetstat == 0 && fl1 == 1 && millis() - timer0 >= 20000) {
    fl1 = 0; fl2 = 1; wifiConnect2(); timer0 = millis(); Serial.println("\n = Connecting Gateway 2 =");}
   
  if (inetstat == 0 && fl2 == 1 && millis() - timer0 >= (20000)) {
    fl2 = 0; fl3 = 1; wifiConnect0(); timer0 = millis(); Serial.println("\n = Connecting Gateway 0 =");}

  if (inetstat == 0 && fl3 == 1 && millis() - timer0 >= (20000)) {
    fl3 = 0; fl1 = 1; wifiConnect1(); timer0 = millis(); Serial.println("\n = Connecting Gateway 1 =");}

  if (Blynk.connected() && fl4 == 1) {
    fl4 = 0;
    Blynk.virtualWrite(vPIN_INETSTAT, GatewayOn);
    Serial.print("\n = Connected via: ");Serial.println(GatewayOn);
  }
}

BLYNK_CONNECTED()   //Подключение к Blynk для считывания счётчиков, кнопок.
{Blynk.syncAll();}  //Синхр. все вирт. пины при подключении к Blynk (включенные в приложении)

void loop() {
  timer.run();
  Blynk.run(); // connect/reconnect и сбор возможных новых данных из приложения
  /* \/ Таймер который не “уходит” \/ */
  if (millis() - count_time >= period) {   // ищем разницу (1000 мс)
    count_time += period;                 // сброс таймера
    digitalWrite(LED, !digitalRead(LED));
//    Serial.print(" RunTime: "); Serial.println(count_time / 1000L);
    Blynk.virtualWrite(vPIN_TIMER, count_time / 1000L);
  }
}

void pingerRUN() {
  // Ping dns
  Serial.printf("\nPinging dns 77.88.8.8 \n");
  if(pinger.Ping(IPAddress(77,88,8,8)) == false)
  {
    Serial.println("Error during ping command.");
  }
}

void PingerSetup() {
pinger.OnReceive([](const PingerResponse& response)
  {
    if (response.ReceivedResponse)
    {
      Serial.printf(
        "Reply from %s: bytes=%d time=%lums TTL=%d\n",
        response.DestIPAddress.toString().c_str(),
        response.EchoMessageSize - sizeof(struct icmp_echo_hdr),
        response.ResponseTime,
        response.TimeToLive);
    }
    else
    {
      Serial.printf("Request timed out.\n");
    }
    // Return true to continue the ping sequence.
    // If current event returns false, the ping sequence is interrupted.
    return true;
  });
  pinger.OnEnd([](const PingerResponse& response)
  {
    // Evaluate lost packet percentage
    float loss = 100;
    if(response.TotalReceivedResponses > 0)
    {
      loss = (response.TotalSentRequests - response.TotalReceivedResponses) * 100 / response.TotalSentRequests;
    }
    // Print packet trip data
    Serial.printf(
      "Ping statistics for %s:\n",
      response.DestIPAddress.toString().c_str());
    Serial.printf(
      "    Packets: Sent = %lu, Received = %lu, Lost = %lu (%.2f%% loss),\n",
      response.TotalSentRequests,
      response.TotalReceivedResponses,
      response.TotalSentRequests - response.TotalReceivedResponses,
      loss);
    // Print time information
    if (response.TotalReceivedResponses > 0)
    {
      Serial.printf("\nApproximate round trip times in milli-seconds:");
      Serial.printf("\nMinimum = %lums, Maximum = %lums, Average = %.2fms\n",
        response.MinResponseTime,
        response.MaxResponseTime,
        response.AvgResponseTime);
        String pingout = pingout + response.AvgResponseTime + " ms";
        Blynk.virtualWrite(vPIN_PINGER, pingout);  // Вывод средний пинг
        inetstat = 1; // Интернет есть, остановка перебора шлюзов.
    }
      else {
        String pingout = "IP timeout!";
        inetstat = 0; // Интернета нет, запуск перебора шлюзов.
        Serial.println(pingout);
        Blynk.virtualWrite(vPIN_PINGER, pingout);
      }
    return true;
  });
}
Просьба за условия if в одну_строчку не ругать, у меня память плохо развита и пока очень сложно считывать код, если он разбит как положено.
 
Изменено: