Веб-сервер Arduino и обнаружение потери сетевого соединения

У меня есть Arduino с экраном Ethernet, и я хочу иметь возможность обнаруживать потерю сетевого соединения (например, если кто-то отключает кабель Ethernet от Arduino). Я искал в Интернете и ничего не нашел. IP-адрес глобального объекта Ethernet остается неизменным даже после отключения кабеля. Любой совет очень ценится!


person Mason    schedule 09.03.2013    source источник


Ответы (2)


Я могу придумать два грубых способа и один сложный. Сначала грубые способы:

DHCP-запрос

Запросите новый DHCP-адрес и проверьте наличие ошибок (при условии, что IP-адрес назначается динамически). Сбой может означать, что кабель отключен (или что ваш DHCP-сервер отключен :)

if (Ethernet.maintain() % 2 == 1) {
    // Cable disconnected or DHCP server hosed
}

пинг/подключение

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

Либо эхо-запрос, либо метод обновления DHCP может быть выполнен, когда вы не подключены к какой-либо службе с интервалом для обнаружения удаления кабеля.

Ссылка на сайт

Похоже, что хотя W5100 (это ядро ​​Ethernet-шилда) имеет сигнал LINKLED, он не делает его доступным для процессора через свои регистры. Если вы хотите припаять провода к своему экрану, вы можете сами построить детектор отключения. Очевидным выбором будет LINKLED, однако он мигает во время активности TX/RX. Поэтому, если вы знаете, что находитесь в сети со скоростью 100 Мбит/с, вы можете припаять провод к SPDLED (прямо под разъемом RJ45), хотя я не могу точно сказать, к какой стороне припаивать. SPDLED имеет активный низкий уровень, поэтому вы можете взять мультиметр и выяснить, какая его сторона ближе всего к 0 В, когда светодиод не горит. Затем вы можете запустить этот провод в свой Arduino и digitalRead() его.

person angelatlarge    schedule 10.03.2013
comment
Метод DHCP всегда возвращает 0 (что странно). Есть мысли по этому поводу? Кроме того, как вы пингуете сервер с Arduino? Я искал, но нашел только ультразвуковые датчики расстояния - person Mason; 10.03.2013
comment
Странно с DHCP. Что ж, вы можете просто попробовать подключиться к какому-нибудь заведомо исправному серверу (скажем, к вашему маршрутизатору или google.com или что-то в этом роде). Если вы не можете подключиться к клиенту, значит либо сеть не работает, либо кабель отключен. Очевидно, ping не является частью библиотеки Ethernet, но некоторые умные люди разработали ICMP ping для Arduino здесь - person angelatlarge; 10.03.2013
comment
Я только что просмотрел код библиотеки Ethernet, и похоже, что maintain() практически ничего не делает, если не нужно продлевать аренду DHCP. Вы можете изменить библиотеку Ethernet, чтобы открыть объект DHCP, и принудительно обновить его, выпустив DHCP, хотя класс DHCP, похоже, не предоставляет методы для этого, поэтому вам придется их написать. Могу я спросить вас, используете ли вы Arduino как сервер или как клиент? Будете ли вы контролировать сетевую инфраструктуру (т. е. используемый маршрутизатор и т. д.)? - person angelatlarge; 10.03.2013
comment
Я использую ардуино в качестве сервера. Я не буду контролировать сетевую инфраструктуру (я учусь в университете и буду в их сети) - person Mason; 10.03.2013
comment
ХОРОШО. Если вы не контролируете инфраструктуру, это немного ограничивает ваши возможности. Похоже, что библиотека Ethernet позволяет одновременно использовать как серверы, так и клиентов, поэтому у вас может быть клиент, который пытается подключиться к какому-то известному серверу (www.google.com) или, что еще лучше, подключиться к самому маршрутизатору (IP-адрес шлюза по умолчанию). Вероятно, вы можете подключиться к нему по протоколу HTTP (подключиться к порту 80), но, возможно, нет: вам нужно будет увидеть, какие порты у него открыты. Если он не принимает соединения ни на одном порту, вы можете подключиться к www.google.com на порту 80. Но локальный маршрутизатор будет лучше, ИМХО. - person angelatlarge; 13.03.2013

У меня есть некоторое сетевое оборудование, которое не терпит изменения общедоступного IP-адреса (и требует перезагрузки, если это произойдет). Этот код обнаружит отсоединение кабеля Ethernet и изменение общедоступного IP-адреса. Заглушки доступны для действий после получения или изменения общедоступного IP-адреса. Я использую плату W5500, которая жестко подключена к Arduino PRO (т. е. не к щиту), и это работает хорошо. Не стесняйтесь использовать это, как вам угодно.

    /*
        ---------------------------------------- IMPORTANT ----------------------------------------

THIS CODE IS DEPENDENT UPON THE ETHERNET LIBRARY 2.0.0, WHICH IS AVAILABLE FOR DOWNLOAD FROM <https://www.arduinolibraries.info/libraries/ethernet2>. ADDITIONAL DOCUMENTATION IS AVIALABLE AT <https://www.pjrc.com/arduino-ethernet-library-2-0-0/>, <https://www.arduino.cc/en/Reference/> AND <https://www.arduino.cc/reference/en/language/functions/communication/stream/>. ------------------------------------------------------------------------------------------- */ #include <SPI.h> #include <TextFinder.h> #include <Dhcp.h> #include <Dns.h> #include <Ethernet.h> #include <EthernetUdp.h> #define kBUILD_TO_DEBUG const int kSPI_CLK_PIN = 13; const int kSPI_MISO_PIN = 12; const int kSPI_MOSI_PIN = 11; const int kETHERNET_CS_PIN = 10; const int kSPI_DEVICE_DISABLED = HIGH; const int kSPI_DEVICE_ENABLED = LOW; const int kRD382_RESET_ASSERTED = HIGH; const int kRD382_RESET_NEGATED = LOW; enum { kPUBLIC_IP_ADDRESS, kLOCAL_IP_ADDRESS } IP_ADDRESS_TYPES; enum { kSTATE_POSITION_WAIT_FOR_LINK = 0, kSTATE_POSITION_GET_DHCP, kSTATE_POSITION_GET_IP, kSTATE_POSITION_RUN_TIMER, kSTATE_POSITION_PERFORM_TASK_ON_ADDRESS_CHANGE, kSTATE_POSITION_SEND_REPORT_ON_ADDRESS_CHANGE, kSTATE_POSITION_FATAL_ERROR } STATE_POSITIONS; typedef struct { IPAddress public_ip_address; IPAddress local_ip_address; unsigned long int currentTime; unsigned long int resetcount; unsigned int millisTimer; int link_status; uint8_t secondsTimer; uint8_t minutesTimer; uint8_t state_position; } GLOBALS; const int kTIMER_INIT_MILLISECONDS = 999; const int kTIMER_INIT_SECONDS = 59; const int kTIMER_INIT_MINUTES = 4; const int kMILLIS_ONE_SECOND_DELAY = 1000; const int kCONNECT_SUCCESS = 1; const int kCONNECT_TIMED_OUT = -1; const int kCONNECT_INVALID_SERVER = -2; const int kCONNECT_TRUNCATED = -3; const int kCONNECT_INVALID_RESPONSE = -4; const int kMAC_UNICAST_MULTICAST_BIT_ADDRESS = 0; const int kMAC_UNICAST = 0; const int kMAC_UNICAST_AND_MASK = ~( 1 << kMAC_UNICAST_MULTICAST_BIT_ADDRESS ); const int kMAC_LOCALLY_ADMINISTERED_BIT_ADDRESS = 1; const int kMAC_LOCALLY_ADMINISTERED = 1; const int kMAC_LOCALLY_ADMINISTERED_OR_MASK = ( kMAC_LOCALLY_ADMINISTERED << kMAC_LOCALLY_ADMINISTERED_BIT_ADDRESS ); const int kMAC_ADDRESS_MSB = '0' & kMAC_UNICAST_AND_MASK | kMAC_LOCALLY_ADMINISTERED_OR_MASK; // If the Ethernet adapter has a MAC address, enter that MAC address into mac_address[]. const byte mac_address[] = {kMAC_ADDRESS_MSB, 'E', 'N', 'E', 'T', '0'}; /* If you would prefer to use your own page to support obtaining the public IP address, the following HTML5 and PHP code can be used as the source for that page: <html><head><title>Current IP Check</title></head><body>Current IP Address: <?php echo $_SERVER['REMOTE_ADDR']; ?></body></html> Then change the addressing constants below as appropriate. */ const char kSERVER_DOMAIN_NAME[] = "checkip.dyndns.org"; const char kSERVER_PAGE_NAME[] = "/"; const char kSERVER_PORT_NUMBER = 80; const char kIP_QUERY_PAGE[] = "GET checkip.dyndns.org "; const char ip_search_str[] = "IP Address: "; const signed int kENET_SUCCESS = 1; const signed int kENET_TIMED_OUT = -1; const signed int kENET_INVALID_SERVER = -2; const signed int kENET_TRUNCATED = -3; const signed int kENET_INVALID_RESPONSE = -4; const int kDEFAULT_CONNECTION_TIMEOUT = 3000; const int kETHERNET_TIMEOUT = 5; const int kIP_QUERY_MINIMUM_SIZE = 244; // Assume length for public IP of 1.1.1.1 const long kIP_QUERY_TIMEOUT = ( 5 * kMILLIS_ONE_SECOND_DELAY ); // seconds, expressed in milliseconds const int kNOTIFICATION_RETRY_COUNT_SEED = 10; const int kSIZE_OF_IPV4_ADDRESS = 4; EthernetClient ethernetClient; TextFinder textFinder ( ethernetClient, kETHERNET_TIMEOUT ); GLOBALS globals; /* -------------------------------------------------------------------------------- */ void address_changed_task ( void ) { // Insert code to be executed when the public IP address changes, or upon the // initial acquisition of the public IP address. #ifdef kBUILD_TO_DEBUG // { if ( Serial ) { Serial.println ( "STATUS: address_changed_task () completed" ); } #endif // } kBUILD_TO_DEBUG } /* -------------------------------------------------------------------------------- */ void send_report ( void ) { // Insert code to report status after handling a change in public IP address #ifdef kBUILD_TO_DEBUG // { if ( Serial ) { Serial.println ( "STATUS: send_report () completed" ); } #endif // } kBUILD_TO_DEBUG } /* -------------------------------------------------------------------------------- */ void clear_ip_address ( uint8_t ip_address_type ) { for ( int index = 0; index < kSIZE_OF_IPV4_ADDRESS; index++ ) { switch ( ip_address_type ) { case kPUBLIC_IP_ADDRESS: globals.public_ip_address[index] = 0; break; case kLOCAL_IP_ADDRESS: globals.local_ip_address[index] = 0; break; } } } // -------------------------------------------------------------------------------- void display_ip_address ( uint8_t ip_address_type ) { #ifdef kBUILD_TO_DEBUG // { if ( Serial ) { switch ( ip_address_type ) { case kPUBLIC_IP_ADDRESS: { Serial.print ( "Public IP Address: " ); for ( int index = 0; index < kSIZE_OF_IPV4_ADDRESS; index++ ) { Serial.print ( globals.public_ip_address[index] ); if ( index < ( kSIZE_OF_IPV4_ADDRESS - 1 ) ) { Serial.print ( "." ); } } } break; case kLOCAL_IP_ADDRESS: { Serial.print ( "Local IP Address: " ); for ( int index = 0; index < kSIZE_OF_IPV4_ADDRESS; index++ ) { Serial.print ( globals.local_ip_address[index] ); if ( index < ( kSIZE_OF_IPV4_ADDRESS - 1 ) ) { Serial.print ( "." ); } } } break; } Serial.println ( "" ); } #endif // } kBUILD_TO_DEBUG } /* -------------------------------------------------------------------------------- */ void display_mac_address ( void ) { #ifdef kBUILD_TO_DEBUG // { uint8_t field_count = ( sizeof ( mac_address ) / sizeof ( mac_address[0] ) ); if ( Serial ) { Serial.print ( "MAC Address: " ); for ( int index = 0; index < field_count; index++ ) { Serial.print ( mac_address[index] ); if ( index < ( field_count - 1 ) ) { Serial.print ( "." ); } } Serial.println ( "" ); } #endif // } kBUILD_TO_DEBUG } /* -------------------------------------------------------------------------------- */ /* A query to http://checkip.dyndns.org will return the following: HTTP/1.1 200 OK Content-Type: text/html Server: DynDNS-CheckIP/1.0 Connection: close Cache-Control: no-cache Pragma: no-cache Content-Length: 105 <html><head><title>Current IP Check</title></head><body>Current IP Address: 50.37.222.230</body></html> */ boolean get_public_ip_address ( void ) { unsigned long int temp_timer = 0; unsigned int retry_count = kNOTIFICATION_RETRY_COUNT_SEED; signed int client_connected_result = kENET_SUCCESS; byte public_ip_address[] = {0,0,0,0}; int availableDataSize; boolean found_ip_address = false; boolean values_matched = true; boolean all_zero = true; boolean result = false; while ( !found_ip_address && ( 0 != retry_count ) ) { ethernetClient.setConnectionTimeout ( kDEFAULT_CONNECTION_TIMEOUT ); client_connected_result = ethernetClient.connect ( (const char*)kSERVER_DOMAIN_NAME, kSERVER_PORT_NUMBER ); if ( kCONNECT_SUCCESS == client_connected_result ) { ethernetClient.println ( kIP_QUERY_PAGE ); temp_timer = kIP_QUERY_TIMEOUT; availableDataSize = ethernetClient.available (); while ( ( kIP_QUERY_MINIMUM_SIZE > availableDataSize ) && ( 0 != temp_timer ) ) { availableDataSize = ethernetClient.available (); delay ( 1 ); if ( 0 < temp_timer ) { temp_timer--; } }; if ( 0 != availableDataSize ) { if ( textFinder.find ( (char*)ip_search_str ) ) { values_matched = true; all_zero = true; for ( int index = 0; index < kSIZE_OF_IPV4_ADDRESS; index++ ) { public_ip_address[index] = textFinder.getValue (); if ( globals.public_ip_address[index] != public_ip_address[index] ) { values_matched = false; globals.public_ip_address[index] = public_ip_address[index]; } if ( 0 != public_ip_address[index] ) { all_zero = false; } } if ( values_matched || !all_zero ) { found_ip_address = true; } if ( !values_matched && !all_zero ) { result = true; } } } ethernetClient.stop (); } if ( 0 != retry_count ) { retry_count--; } } return result; } /* -------------------------------------------------------------------------------- */ boolean ip_address_is_zero ( uint8_t ip_address_type ) { boolean result = true; for ( int index = 0; index < kSIZE_OF_IPV4_ADDRESS; index++ ) { switch ( ip_address_type ) { case kPUBLIC_IP_ADDRESS: if ( 0 != globals.public_ip_address[index] ) { result = false; } break; case kLOCAL_IP_ADDRESS: if ( 0 != globals.local_ip_address[index] ) { result = false; } break; } } return result; } /* -------------------------------------------------------------------------------- */ void setup () { globals.state_position = kSTATE_POSITION_WAIT_FOR_LINK; #ifdef kBUILD_TO_DEBUG // { Serial.begin ( 115200 ); while ( !Serial ) {} #endif // } kBUILD_TO_DEBUG clear_ip_address ( kPUBLIC_IP_ADDRESS ); clear_ip_address ( kLOCAL_IP_ADDRESS ); pinMode ( kETHERNET_CS_PIN, OUTPUT ); digitalWrite ( kETHERNET_CS_PIN, kSPI_DEVICE_DISABLED ); display_mac_address (); /* Note that the following execution of Ethernet.begin ( mac ) will attempt to obtain a local IP address via DHCP. If the Ethernet cable is not attached, the Ethernet.begin method will timeout after 60-seconds. Aside from delaying the completion of setup (), this delay imposes no bad behavior. The Ethernet.hardwareStatus () method does not function until after Ethernet.begin has executed so setup () invokes Ethernet.begin regardless of any status. It would be desirable to invoke Ethernet.hardwareStatus () to determine if Ethernet.linkStatus () is supported, and then execute Ethernet.begin ( mac ) only if the LINK is active (i.e. cable is attached) when the hardware supports such a determination (a value of EthernetNoHardware is returned). The current Ethernet library does not support this sequence (at least using a W5500 device). If this were possible, the invoking of Ethernet.begin ( mac ) would simply defer execution to the state machine and avoid execution in setup (). */ Ethernet.begin ( (byte*)mac_address ); } /* -------------------------------------------------------------------------------- The STATE MACHINE will detect attachment and detachment of the ethernet cable, and initiate IP polling upon attachment of the cable. IP Polling will be suspended when the ethernet cable is detached. For the W5100, where no link (cable) detection is supported, the STATE MACHINE will act as if the ethernet cable is always attached. Nominal state machine execution occurs at a minimum of 1-second intervals, with the interval being extended by any task execution time. */ void state_machine ( void ) { int link_on_status = Unknown; uint8_t current_state_position = globals.state_position; switch ( Ethernet.hardwareStatus () ) { case EthernetNoHardware: globals.state_position = kSTATE_POSITION_FATAL_ERROR; break; case EthernetW5100: link_on_status = LinkON; break; case EthernetW5200: link_on_status = Ethernet.linkStatus (); break; case EthernetW5500: link_on_status = Ethernet.linkStatus (); break; } switch ( globals.state_position ) { case kSTATE_POSITION_WAIT_FOR_LINK: { /* This is the initial state position. Global variables are initialized to prepare for proper checks to occur once the ethernet link is determined to be active. */ globals.minutesTimer = 0; globals.secondsTimer = 0; if ( !ip_address_is_zero ( kLOCAL_IP_ADDRESS ) ) { clear_ip_address ( kPUBLIC_IP_ADDRESS ); clear_ip_address ( kLOCAL_IP_ADDRESS ); display_ip_address ( kPUBLIC_IP_ADDRESS ); display_ip_address ( kLOCAL_IP_ADDRESS ); } if ( LinkON == link_on_status ) { globals.state_position = kSTATE_POSITION_GET_DHCP; } } break; case kSTATE_POSITION_GET_DHCP: { /* Upon attachment of the Ethernet cable, acquire a new DHCP address. Execution of the Ethernet.begin ( mac ) method may seem redundant, but occurs in case the cable has been unplugged for sufficient time that the DHCP lease has expired. It is possible that a new address is required. */ if ( LinkON == link_on_status ) { Ethernet.begin ( (byte*)mac_address ); globals.local_ip_address = Ethernet.localIP (); if ( !ip_address_is_zero ( kLOCAL_IP_ADDRESS ) ) { display_ip_address ( kLOCAL_IP_ADDRESS ); globals.state_position = kSTATE_POSITION_GET_IP; } } else if ( LinkOFF == link_on_status ) { globals.state_position = kSTATE_POSITION_WAIT_FOR_LINK; } } break; case kSTATE_POSITION_GET_IP: { /* This state position obtains the public IP address by issuing a query to 'checkip.dyndns.org'. The invoked function will parse the returned data to obtain the public IP address. */ if ( LinkON == link_on_status ) { if ( get_public_ip_address () ) { display_ip_address ( kPUBLIC_IP_ADDRESS ); globals.state_position = kSTATE_POSITION_PERFORM_TASK_ON_ADDRESS_CHANGE; } else { globals.state_position = kSTATE_POSITION_RUN_TIMER; } Ethernet.maintain (); } else if ( LinkOFF == link_on_status ) { globals.state_position = kSTATE_POSITION_WAIT_FOR_LINK; } } break; case kSTATE_POSITION_RUN_TIMER: { /* This state position checks whether it is time to check if the public IP address has changed. */ if ( LinkON == link_on_status ) { if ( 0 == globals.secondsTimer ) { globals.secondsTimer = kTIMER_INIT_SECONDS; } if ( 0 == globals.minutesTimer ) { globals.minutesTimer = kTIMER_INIT_MINUTES; } globals.secondsTimer--; if ( 0 == globals.secondsTimer ) { globals.minutesTimer--; if ( 0 == globals.minutesTimer ) { globals.state_position = kSTATE_POSITION_GET_IP; } globals.secondsTimer = kTIMER_INIT_SECONDS; } Ethernet.maintain (); } else if ( LinkOFF == link_on_status ) { globals.state_position = kSTATE_POSITION_WAIT_FOR_LINK; } } break; case kSTATE_POSITION_PERFORM_TASK_ON_ADDRESS_CHANGE: { /* This state position will invoke the function to handle a change to the public IP address, or upon initial acquisition of the public IP address (either on start up or when the ethernet cable is attached). */ if ( LinkON == link_on_status ) { address_changed_task (); globals.state_position = kSTATE_POSITION_SEND_REPORT_ON_ADDRESS_CHANGE; Ethernet.maintain (); } else if ( LinkOFF == link_on_status ) { globals.state_position = kSTATE_POSITION_WAIT_FOR_LINK; } } break; case kSTATE_POSITION_SEND_REPORT_ON_ADDRESS_CHANGE: { /* This state position will invoke the function to report completion of handling a change to the public IP address. */ if ( LinkON == link_on_status ) { send_report (); globals.state_position = kSTATE_POSITION_RUN_TIMER; Ethernet.maintain (); } else if ( LinkOFF == link_on_status ) { globals.state_position = kSTATE_POSITION_WAIT_FOR_LINK; } } break; case kSTATE_POSITION_FATAL_ERROR: { /* This state machine requires Ethernet hardware that can report the ethernet link status. If the hardware lacks this support, this state is entered and purposefully hangs. */ #ifdef kBUILD_TO_DEBUG // { if ( Serial ) { Serial.println ( "FATAL ERROR: Ethernet hardware NOT FOUND" ); } #endif // } kBUILD_TO_DEBUG while ( true ) {}; } break; } } /* -------------------------------------------------------------------------------- */ /* State machine execution occurs at a minimum interval of 1000 milliseconds. */ void loop () { unsigned long int currentTime = millis (); if ( globals.currentTime != currentTime ) { globals.currentTime = currentTime; if ( ( 0 == globals.millisTimer ) || ( kTIMER_INIT_MILLISECONDS < globals.millisTimer ) ) { globals.millisTimer = kTIMER_INIT_MILLISECONDS; } globals.millisTimer--; if ( 0 == globals.millisTimer ) { state_machine (); globals.millisTimer = kTIMER_INIT_MILLISECONDS; } } }
person RayM    schedule 21.10.2018
comment
Кстати, реальная проблема заключается в том, что метод hardwareStatus() библиотеки Ethernet не работает до тех пор, пока не будет выполнен метод begin (mac). Если бы hardwareStatus() работал правильно, 60-секундной блокировки в методе begin ( mac ) можно было бы избежать, вызывая этот метод только при подключении кабеля. - person RayM; 24.10.2018