【问题标题】:Arduino Web Server and detecting loss of network connectionArduino Web 服务器和检测网络连接丢失
【发布时间】:2013-03-09 23:35:45
【问题描述】:

我有一个带有以太网屏蔽的 arduino,我希望能够检测到网络连接丢失(即,如果有人从 arduino 断开以太网电缆)。我在网上四处寻找,没有发现任何东西。以太网全局对象的 IP 地址保持不变,即使在拔下电缆后也是如此。非常感谢任何建议!

【问题讨论】:

    标签: networking webserver arduino ethernet


    【解决方案1】:

    我可以想到两种粗略的方法和一种艰难的方法。粗略的方法优先:

    DHCP 请求

    请求一个新的 DHCP 地址并检查是否失败(假设 IP 地址是动态分配的)。故障可能意味着电缆已断开(或者您的 DHCP 服务器已被软管:)

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

    ping/连接

    Ping 一个已知地址,可能是路由器。如果它失败了,你就没有连接,要么是因为电缆被拔掉了,要么是因为其他原因(路由器停机、网络停机等)。

    当您没有连接到某些服务时,可以执行 ping 或 DHCP 更新方法以检测电缆移除。

    链接

    看起来虽然 W5100(以太网屏蔽的这个核心)有一个LINKLED 信号,但它并没有通过其寄存器向处理器提供这个信号。如果您愿意将电线焊接到屏蔽层上,您可以自己构建一个断线检测器。一个明显的选择是LINKLED,但是,它会在 TX/RX 活动期间闪烁。因此,如果您知道您在 100Mbps 网络上,您可以将一根电线焊接到 SPDLED(就在 RJ45 连接器下方),但我无法确定您应该焊接到哪一侧。 SPDLED 处于低电平有效状态,因此您可以拿起万用表,在 LED 熄灭时找出它的哪一侧最接近 0V。然后,您可以将这条线连接到您的 Arduino 中并 digitalRead() 它。

    【讨论】:

    • DHCP 方法总是返回 0(这很奇怪)。对此有什么想法吗?另外,您如何从 Arduino ping 服务器?我看了但只找到了超声波距离传感器
    • DHCP 很奇怪。好吧,你可以简单地尝试连接到一些已知的好服务器(比如你的路由器或google.com 或类似的东西)。如果您无法与客户端连接,则网络已关闭或电缆已断开连接。显然 ping 不是以太网库的一部分,但是一些聪明人确实为 Arduino 开发了 ICMP ping here
    • 我刚刚查看了以太网库代码,看起来maintain() 基本上什么都不做,如果您的 DHCP 租约不需要更新的话。您可以修改以太网库以公开 DHCP 对象,并通过释放 DHCP 强制它更新,尽管 DHCP 类似乎没有为此提供方法,因此您必须编写这些方法。请问您是使用 Arduino 作为服务器还是客户端?你会控制网络基础设施(即使用的路由器等)吗?
    • 我使用 arduino 作为服务器。我不会控制网络基础设施(我在大学,我会在他们的网络上)
    • 好的。如果您不控制限制您选择的基础设施。看起来以太网库同时允许服务器和客户端,因此您可以有一个客户端尝试连接到某个已知服务器(www.google.com),或者更好的是,连接到路由器本身(默认网关 IP 地址)。您可能可以通过 HTTP 访问它(在端口 80 上连接),但可能不行:您必须查看它打开了哪些端口。如果它不接受任何端口上的连接,您可以连接到端口 80 上的www.google.com。但是本地路由器会更好,恕我直言。
    【解决方案2】:

    我有一些不能容忍公共 IP 地址更改的联网硬件(如果发生这种情况,需要重新启动)。此代码将检测以太网电缆是否断开以及公共 IP 地址的变化。存根任务可用于对公共 IP 地址的获取或更改进行操作。我使用的是硬连线到 Arduino PRO(即不是屏蔽)的 W5500 板,这很好用。请随意使用。

    /* - - - - - - - - - - - - - - - - - - - - 重要的 - - - - - ------------------------------------------- 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; } } }

    【讨论】:

    • 顺便说一句,真正的问题是以太网库 hardwareStatus () 方法在执行 begin ( mac ) 方法之前不起作用。如果 hardwareStatus () 正常运行,则可以通过仅在插入电缆时调用该方法来避免 begin ( mac ) 方法中的 60 秒阻塞。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-26
    • 2010-10-10
    • 2013-09-02
    • 1970-01-01
    • 1970-01-01
    • 2021-02-25
    相关资源
    最近更新 更多