• Система автоматизации с открытым исходным кодом на базе esp8266/esp32 микроконтроллеров и приложения IoT Manager. Наша группа в Telegram

Нужна помощь Определение наличия подключения к сети WiFi

iG2019

New member
Нужно чтобы ESP при включении работала не зависмо от наличия сети WiFi, т.е. снимала показания датчиков, щелкала реле и т.д. Как только появляется сеть - подключалась к серверу mqtt и передавала на него показания. В случае пропадания сети продолжала нормальную работу.
Начал с простенького кода
Код:
#include <OLED_I2C.h>
#include <ESP8266WiFi.h>            
#include <WiFiClient.h>

char *ssid = "********"; // Имя роутера
char *pass = "********"; // Пароль роутера

OLED  myOLED(D2, D1); 

extern uint8_t SmallFont[];
extern uint8_t MediumNumbers[];
extern uint8_t BigNumbers[];
void setup() {
   Serial.begin(115200);
  WiFi.begin(ssid, pass);

if(!myOLED.begin(SSD1306_128X32))
 while(1);   // In case the library failed to allocate enough RAM for the display buffer...

  myOLED.setFont(SmallFont);

}

void loop() {
  if(WiFi.status() == WL_CONNECTED) {
digitalWrite(LED_BUILTIN, LOW);
Serial.println("WiFi");}
    }
  
  if(WiFi.status() != WL_CONNECTED) { 
    digitalWrite(LED_BUILTIN, HIGH);
  Serial.println("NO WiFi");
  }

}
Включаю ESP - светодиод загорается, в сериал сыпятся сообщения о наличии WiFi. Выключаю на роутере WiFi - светодиод гаснет, в порт сыпятся сообщения об отсутствии сети. Опять включаю на роутере сеть и ... вот здесь начинаются непонятки. ESP может подключится к сети (светодиод загорается), а может и не подключиться. Аналогичная ситуация происходит если включить ESP при отсутствии сети WiFi. Какой либо закономерности не заметил.
Подскажите что я делаю не так?
 

CodeNameHawk

Moderator
Команда форума
Так наверное вы все практически и сделали. События отключения и подключения отстают от фактического состояния сети.
Установите свойства autoconnect и autoreconect, есп будет сама пере подключаться к сети.
 

enjoynering

Well-known member
WiFi.status() == WL_CONNECTED показывает только наличие соединения с точкой доступа/роутером, а у него то как раз связи с инетом может не быть :). Вам нужен ping

перед отправкой пингуете google.com или православный yandex.ru и если ответ пришел значит интернет есть. Неплохая библиотека обертка ping для arduino esp8266 framework-а ТУТ
 

nikolz

Well-known member
если сеть формируется роутером, то в общем случае надо определять три события
1) работает ли роутер
2) тот ли адрес дал вам роутер (нужный адрес, если он задан фиксировано в ESP, может быть занят)
3) работает в сети устройство с котором обмениваемся данными
подумайте над этим
 

CodeNameHawk

Moderator
Команда форума
1) работает ли роутер
2) тот ли адрес дал вам роутер (нужный адрес, если он задан фиксировано в ESP, может быть занят)
3) работает в сети устройство с котором обмениваемся данными
подумайте над этим
Первые два пункты и проверяются WiFi.status().
Насчет третьего, важно проверять, принял ли сервер посланные данные, а не доступен ли он.
Проверка доступен ли сервер ничего не дает, проверка может пройти, а следующее обращение к нему может не пройти.
 

kotyara12

New member
Не знаю, пригодится ли... Но я делаю это примерно так (см. ниже).
wifiLoop() вставляю в основной цикл, вся "работа внутри" соединения выполняется в callback функции _cbWiFiConnWork().
В этом случае ESP в случае обрыва соединения самостоятельно его восстановит, а при отсутствии - может спокойно функционировать и без интернета, автоматика спокойно работает в автономном режиме.

Код:
typedef enum {
  wifiNotConnected, wifiConnectWait, wifiConnectInit, wifiConnectWork
} wifiConnStage_t; 

bool wifiConnect() 
{
  _ledWiFi->ledBlinkOn(_wifiBlinkConn);

  _wifiTryConnect++;

  Serial.print(F("WiFi :: Connect to network '"));
  Serial.print(_wifiSSID);
  Serial.print(F("', try "));
  Serial.print(_wifiTryConnect);
  Serial.print(F(" "));

  #if defined(ESP32) 
    // -- ESP32 :: begin --------------------------------------------------
    _wifiConnectedEvent = WiFi.onEvent(onWiFiConnected, WiFiEvent_t::SYSTEM_EVENT_STA_CONNECTED);
    _wifiGotIPEvent = WiFi.onEvent(onWiFiGotIP, WiFiEvent_t::SYSTEM_EVENT_STA_GOT_IP);
    if (_wifiDisconnectedEvent != 0) WiFi.removeEvent(_wifiDisconnectedEvent);
    _wifiDisconnectedEvent = 0;
    // -- ESP32 :: end ----------------------------------------------------
  #else
    // -- ESP8266 :: begin ------------------------------------------------
    _wifiConnectedHandler = WiFi.onStationModeConnected(onWiFiConnected);
    _wifiGotIPHandler = WiFi.onStationModeGotIP(onWiFiGotIP);
    _wifiDisconnectedHandler = nullptr;
    // -- ESP8266 :: end --------------------------------------------------
  #endif

  WiFi.mode(WIFI_STA);
  WiFi.setAutoReconnect(false);
  #if defined(ESP32) 
    // -- ESP32 :: begin --------------------------------------------------
    WiFi.begin(_wifiSSID.c_str(), _wifiPass.c_str(), 0, NULL, false);
    // -- ESP32 :: end ----------------------------------------------------
  #else
    // -- ESP8266 :: begin ------------------------------------------------
    WiFi.begin(_wifiSSID, _wifiPass, 0, NULL, false);
    // -- ESP8266 :: end --------------------------------------------------
  #endif
 
  _wifiStage = wifiConnectWait;
  wifiConnTimerStart();
  WiFi.reconnect();
}

bool wifiConnectionFailed(wl_status_t wifiStatus, const byte printStatus) 
{
  Serial.println();
  Serial.println(F("WiFi :: Connection failed!"));
  if (printStatus > 0) {
    wifiPrintStatus(wifiStatus, printStatus > 1); 
  };
 
  wifiConnTimerStop();
  WiFi.disconnect(true);

  #if defined(ESP32) 
    // -- ESP32 :: begin --------------------------------------------------
    if (_cbWiFiConnLost != nullptr) {
      _cbWiFiConnLost(WIFI_REASON_UNSPECIFIED);
    };
    // -- ESP32 :: end ----------------------------------------------------
  #else 
    // -- ESP8266 :: begin ------------------------------------------------
    if (_cbWiFiConnLost != nullptr) {
      _cbWiFiConnLost(WIFI_DISCONNECT_REASON_UNSPECIFIED);
    };
    // -- ESP8266 :: end --------------------------------------------------
  #endif

  if (_wifiTryConnect >= _wifiTryConnectMax) {
    Serial.println();
    Serial.println(F("*****************************************************************************************"));
    Serial.println(F("$$$                                    SYSTEM RESTART                                 $$$"));
    Serial.println(F("*****************************************************************************************"));
   
    wifiConnTimerStop();
    fixResetReason(RR_WIFI_CONN_ATTEMPTS_EXCEEDED);
    ESP.restart();
  };
}

bool wifiLoop()
{
  bool _result = false;

  // Проверяем статус подключения к WiFi
  wl_status_t _wifiStatus = WiFi.status();
 
  // Подключение к WiFi еще не было установлено
  if ((_wifiStatus == WL_IDLE_STATUS) || (_wifiStatus == WL_DISCONNECTED)) {
    switch (_wifiStage) {
      // Нет подключения, запускаем процедуру соединяемся...
      case wifiNotConnected:
        wifiConnect(); 
        break;

      // В процессе подключения...
      case wifiConnectWait:
        yield();
        // -- ESP8266 ONLY ------------------------------------------------------
        #if defined(ESP8266)
          // -- ESP8266 :: begin ------------------------------------------------
          if (_connTimer.onRestart()) {
            wifiConnTimerDo();
          };
          // -- ESP8266 :: end --------------------------------------------------
        #endif
        // -- ESP8266 ONLY ------------------------------------------------------
        break;

      // Что-то пошло не так, пожалуй лучше всего будет сбросить соединение
      default:
        wifiConnectionFailed(_wifiStatus, 2);
        break;
      };
  }
 
  // Подключение к WiFi установлено
  else if (_wifiStatus == WL_CONNECTED) {
    switch (_wifiStage) {
      // Соединение установлено только что
      case wifiConnectWait:
        _result = true;
        // Меняем частоту мигания светодиода
        _ledWiFi->ledBlinkOn(_wifiBlinkInit);
        // Останавливаем таймер подключения и сбрасываем счетчики
        wifiConnTimerStop();
        _wifiTimeSyncRun = 0;
        _wifiStage = wifiConnectInit;
        break;

      // Соединение установлено в предыдущем рабочем цикле
      case wifiConnectInit:
        // Синхронизация времени с NTP, но только если она еще не была ни разу запущена
        if (year() < 2000) {
          // Считаем количество попыток синхронизации времени (так как она почему-то иногда не проходит совсем)
          _wifiTimeSyncRun++;
          if (_wifiTimeSyncRun > _wifiTimeSyncRunMax) {
            // Не удалось синхронизировать время - собрасываем соединение
            wifiConnectionFailed(_wifiStatus, 0);
          }
          else {
            // Запускаем синхронизацию времени c NTP
            _result = true;
            ntpTimeSyncRun();
          };
        }
        else {
          // Ставим отметку, что инициализация завершена
          _wifiStage = wifiConnectWork;
          // Сбрасываем счетчик попыток соединения
          _wifiTryConnect = 0;
          // Выполняем процедуру при установлении соединения
          if (_cbWiFiConnInit != nullptr) {
            _result = true;
            _cbWiFiConnInit(_wifiFisrtConnect);
          };
          // Сбрасываем флаг _wifiFisrtConnect
          if (_wifiFisrtConnect) {
            _wifiFisrtConnect = false;
          };
          // Отключаем мигание светодиода
          _ledWiFi->ledBlinkOff();
        };
        break;
     
      // Соединение установлено и инициализировано
      case wifiConnectWork:
        // Выполняем процедуру при постоянном соединении
        if (_cbWiFiConnWork != nullptr) {
          _result = true;
          _cbWiFiConnWork();
        };
        break;

      // Другое состояние
      default:
        wifiConnectionFailed(_wifiStatus, 2);
        break;
    };
  }

  // Ошибки : WL_NO_SSID_AVAIL, WL_CONNECTION_LOST, WL_CONNECT_FAILED, WL_SCAN_COMPLETED
  else {
    wifiConnectionFailed(_wifiStatus, 1);
  };

  return _result;
};
Я не утверждаю, что код идеален. Нет, он далек от совершенства, но я над ним постоянно работаю :)

Выглядит это примерно так:

Код:
16:30:41.382 -> Initialization...
16:30:41.382 -> Sketch version: 20190628.01
16:30:41.382 -> Inititailize PINs...
16:30:41.382 -> Inititailize WiFi connection...
16:30:41.460 -> Inititailize MQTT connection...
16:30:41.460 -> Inititailize NTP...
16:30:41.460 -> Inititailize sensors...
16:30:41.460 -> Read settings from EEPROM...
16:30:41.460 -> syncRequests = 1
16:30:41.460 -> intvPublishSensors = 300
16:30:41.492 -> publThingSpeak = 1
16:30:41.492 -> Start timers...
16:30:41.492 -> Initialization complete
16:30:41.492 ->
16:30:41.492 -> WiFi :: Connect to network 'Kotyara12', try 1 .
16:30:42.748 -> WiFi :: Connected to ssid 'Kotyara12', channel 12, rssi: -61
16:30:43.054 -> WiFi :: Local IP address: 192.168.1.41
16:30:43.088 -> NTP :: Transmit NTP request to 'ntp1.stratum2.ru' / '88.147.254.230': ok
16:30:43.292 -> NTP :: Current time: 29.06.2019 16:30:48
16:30:43.292 -> TELEGRAM :: Message " Устройство запущено, версия скетча 20190628.01" :: added to queue, total 1 messages
16:30:43.292 -> TELEGRAM :: Send message " Устройство запущено, версия скетча 20190628.01": HTTP/1.1 200 OK
16:30:46.348 -> MQTT :: Connecting to MQTT server '********:***************@m**.cloudmqtt.com:******'...
16:30:46.958 -> MQTT :: Connection completed
16:30:47.264 -> MQTT :: publish '/village/smarthome/sync_status': "1" [0,1] :: ok
16:30:47.366 -> MQTT :: subscribe on topic '/village/smarthome/sync_status'
16:30:47.774 -> MQTT :: subscribe on topic '/village/smarthome/ota'
16:30:48.080 -> MQTT :: subscribe on topic '/village/smarthome/settings/+'
16:30:48.386 -> MQTT :: subscribe on topic '/village/smarthome/settings/+/+'
Если нужно, могу выслать весь модуль
 

nikolz

Well-known member
В SDK есть пример колбека
который позволяет отследить 10(десять) событий Wi-Fi.
 

iG2019

New member
События отключения и подключения отстают от фактического состояния сети.
это я уже понял и сам, но почему то приведенное мной решение не всегда срабатывает.

WiFi.status() == WL_CONNECTED показывает только наличие соединения с точкой доступа/роутером, а у него то как раз связи с инетом может не быть
так мне и не важно есть ли инет. Сервер может быть и локальным

В SDK есть пример колбека
который позволяет отследить 10(десять) событий Wi-Fi.
Можете ткнуть носом конкретно где посмотреть?

з/ы меня бы вполне устроило "стандартное" решение в setup
Код:
while (WiFi.status() != WL_CONNECTED) {
     delay(500);
     Serial.print(".");
но оно работает только в условиях стабильной сети WiFi. Если же сеть отсутствует в момент включения ESP то вся работа тормозится.
 

nikolz

Well-known member
iG2019,
стр 58
рекомендую подробно изучить этот док
в нем есть ответы на все вопросы по использованию ESP8266
---------------------
список событий найдете в файле user_interface.h их всего 11
 

Вложения

iG2019

New member
объяснили бы еще такому рукожопу как я как этим пользоваться...
Вставил пример из SDK в свой код
Код:
#include <ESP8266WiFi.h>
#include <WiFiClient.h>

char *ssid = "********"; // Имя роутера
char *pass = "********"; // Пароль роутера

void setup() {
  Serial.begin(115200);
  WiFi.begin(ssid, pass);
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  if (WiFi.status() == WL_CONNECTED) {
    digitalWrite(LED_BUILTIN, LOW);
  }


  if (WiFi.status() != WL_CONNECTED) {
    digitalWrite(LED_BUILTIN, HIGH);
  }

void wifi_handle_event_cb(System_Event_t *evt);


}



void wifi_handle_event_cb(System_Event_t *evt)
{
  os_printf("event %x\n", evt->event);
  switch (evt->event) {
    case EVENT_STAMODE_CONNECTED:
      os_printf("connect to ssid %s, channel %d\n",
                evt->event_info.connected.ssid,
                evt->event_info.connected.channel);
      break;
    case EVENT_STAMODE_DISCONNECTED:
      os_printf("disconnect from ssid %s, reason %d\n",
                evt->event_info.disconnected.ssid,
                evt->event_info.disconnected.reason);
      break;
    case EVENT_STAMODE_AUTHMODE_CHANGE:
      os_printf("mode: %d -> %d\n",
                evt->event_info.auth_change.old_mode,
                evt->event_info.auth_change.new_mode);
      break;
    case EVENT_STAMODE_GOT_IP:
      os_printf("ip:" IPSTR ",mask:" IPSTR ",gw:" IPSTR,
                IP2STR(&evt->event_info.got_ip.ip),
                IP2STR(&evt->event_info.got_ip.mask),
                IP2STR(&evt->event_info.got_ip.gw));
      os_printf("\n");
      break;
    case EVENT_SOFTAPMODE_STACONNECTED:
      os_printf("station: " MACSTR "join, AID = %d\n",
                MAC2STR(evt->event_info.sta_connected.mac),
                evt->event_info.sta_connected.aid);
      break;
    case EVENT_SOFTAPMODE_STADISCONNECTED:
      os_printf("station: " MACSTR "leave, AID = %d\n",
                MAC2STR(evt->event_info.sta_disconnected.mac),
                evt->event_info.sta_disconnected.aid);
      break;
    default:
      break;
  }
}
если я правильно понял то в сериал должны печататься события, но там пусто ...
 

nikolz

Well-known member
объяснили бы еще такому рукожопу как я как этим пользоваться...
Вставил пример из SDK в свой код
Код:
#include <ESP8266WiFi.h>
#include <WiFiClient.h>

char *ssid = "********"; // Имя роутера
char *pass = "********"; // Пароль роутера

void setup() {
  Serial.begin(115200);
  WiFi.begin(ssid, pass);
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  if (WiFi.status() == WL_CONNECTED) {
    digitalWrite(LED_BUILTIN, LOW);
  }


  if (WiFi.status() != WL_CONNECTED) {
    digitalWrite(LED_BUILTIN, HIGH);
  }

void wifi_handle_event_cb(System_Event_t *evt);


}



void wifi_handle_event_cb(System_Event_t *evt)
{
  os_printf("event %x\n", evt->event);
  switch (evt->event) {
    case EVENT_STAMODE_CONNECTED:
      os_printf("connect to ssid %s, channel %d\n",
                evt->event_info.connected.ssid,
                evt->event_info.connected.channel);
      break;
    case EVENT_STAMODE_DISCONNECTED:
      os_printf("disconnect from ssid %s, reason %d\n",
                evt->event_info.disconnected.ssid,
                evt->event_info.disconnected.reason);
      break;
    case EVENT_STAMODE_AUTHMODE_CHANGE:
      os_printf("mode: %d -> %d\n",
                evt->event_info.auth_change.old_mode,
                evt->event_info.auth_change.new_mode);
      break;
    case EVENT_STAMODE_GOT_IP:
      os_printf("ip:" IPSTR ",mask:" IPSTR ",gw:" IPSTR,
                IP2STR(&evt->event_info.got_ip.ip),
                IP2STR(&evt->event_info.got_ip.mask),
                IP2STR(&evt->event_info.got_ip.gw));
      os_printf("\n");
      break;
    case EVENT_SOFTAPMODE_STACONNECTED:
      os_printf("station: " MACSTR "join, AID = %d\n",
                MAC2STR(evt->event_info.sta_connected.mac),
                evt->event_info.sta_connected.aid);
      break;
    case EVENT_SOFTAPMODE_STADISCONNECTED:
      os_printf("station: " MACSTR "leave, AID = %d\n",
                MAC2STR(evt->event_info.sta_disconnected.mac),
                evt->event_info.sta_disconnected.aid);
      break;
    default:
      break;
  }
}
если я правильно понял то в сериал должны печататься события, но там пусто ...
это функция колбека wifi
колбеки это такие функции, которые вызывает не пользователь а система
чтобы система знала что эту функцию надо вызвать при наступлении события
надо эту функцию зарегистрировать
регистрация делается один раз
при инициализации
в примере на стр 58 это void user_init() - в самом конце
но это для СИ
для дурины
можно поставить
wifi_set_event_hander_cb(wifi_handle_event_cb);
в setup
---------------------------
оговорюсь, что я на дурине не пишу поэтому возможно и не сработает
можно поискать по документации дурины как регистрировать колбеки
--------------------
 

iG2019

New member
на дурине возможно и не сработает
похоже что да, не работает
Пример WiFiEvents посмотрите.
Порылся в нете по WiFiEventHandler и кое-что нашел WiFiEventHandler gotIpEventHandler, disconnectedEventHandler; Это, думаю, уже можно будет применить
 

=AK=

New member
Нужно чтобы ESP при включении работала не зависмо от наличия сети WiFi, т.е. снимала показания датчиков, щелкала реле и т.д. Как только появляется сеть - подключалась к серверу mqtt и передавала на него показания. В случае пропадания сети продолжала нормальную работу.
Вот пример скетча для Wemos, который это делает. В сетапе он пытается подключиться к WiFi в течении 10 сек, если не получилось - уходит в нормальный режим работы и время от времени пытается подключиться снова и снова (см. coos_task_wifi_reconnect). При пропадании WiFi точно так же, старается снова подключиться.

Когда есть соединение с WiFi , то другая задача, coos_task_mqtt_reconnect, аналогично, следит за соединением с MQTT брокером и переподключается, если связь потеряна.

Параллельно и независимо от этого скетч снимает показания с датчиков, выводит сообщения на свой дисплей, работает с локальной сетью, и т.п. Все это работает на простенькой кооперативной операционной системе, что позволило отделить задачи друг от друга и выполнять иx квази-одновременно.
 

enjoynering

Well-known member
Вот пример скетча для Wemos, который это делает. В сетапе он пытается подключиться к WiFi в течении 10 сек, если не получилось - уходит в нормальный режим работы и время от времени пытается подключиться снова и снова (см. coos_task_wifi_reconnect). При пропадании WiFi точно так же, старается снова подключиться.
очень не плохо, но появился вопрос. зачем это самописный велосипед coos, когда в arduino esp8266 давно есть ticker?
 

=AK=

New member
очень не плохо, но появился вопрос. зачем это самописный велосипед coos, когда в arduino esp8266 давно есть ticker?
Вот этот? Без единого слова описания? Он только для ESP, а coos - для любой Ардино, включая ESP.Причем неизвестно кто из них раньше появился, поскольку на всяких разных платформах coos используется уже лет 15, наверное, с небольшими вариациями.

Или вот этот? Который позволяет вызывать функции через равные интервалы времени? В нем нет гибкости.

coos позволяет варировать задержки по мере необходимости, строить сложные автоматы состояния и циклограммы, в том числе с программно управляемыми задержками, а тикер этого не умеет. Причем тикеры ничуть не в меньшей степени "самописные велосипеды", разница только в том, что более широко известны.

Все-таки coos, хоть и примитивная, но все-таки операционная система, с переключением задач и службой времени, а не просто разновидность таймера Дописав пару строчек кода, в coos можно добавить функциональность, позволяющую задачам останавливать и запускать друг друга, и т.п. Этого не сделано чтобы не пудрить мозги, кому надо - сам сделает.
 
Сверху Снизу