├── examples └── addPortMapping │ └── addPortMapping.ino ├── README.md ├── locateIgd.h ├── portMapping.h ├── portMapping.cpp └── locateIgd.cpp /examples/addPortMapping/addPortMapping.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Add Port Mapping to UPNP device port mapping table 3 | 4 | This example sketch uses the UPNP functionality in most modern commercial routers to add a port mapping. 5 | The port mapping allows an external service to contact the LAN based device via the mapped port. 6 | Logic Steps: 7 | 1) Send an M-SEARCH broadcast request for a UPNP compliant Internet Gateway Device. 8 | 2) Parse any device IGD request response for control URL and port number for the IGD 9 | 3) Send an Add Port Mapping request to the IGD 10 | 4) Optionally request the IGD for the external (public) IP address of the IGD 11 | 12 | 13 | created 08 Aug 2012 14 | by Deverick McIntyre 15 | version 1.0 16 | 17 | */ 18 | 19 | #include 20 | #include 21 | #include "portMapping.h" 22 | 23 | //declare the client object here so it is global in scope so we can call it in functions in order to print 24 | EthernetClient client; 25 | 26 | PortMapClass portmap; 27 | 28 | // Enter a MAC address and IP address for your controller below. 29 | // The IP address will be dependent on your local network, only used if DHCP fails 30 | byte mac[] = { 31 | 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; 32 | IPAddress ip(192,168,0, 177); 33 | 34 | 35 | /* 36 | core Arduino functions 37 | */ 38 | 39 | void setup() { 40 | // Open serial communications and wait for port to open: 41 | Serial.begin(9600); 42 | while (!Serial) { 43 | ; // wait for serial port to connect. Needed for Leonardo only 44 | } 45 | 46 | //send first message after serial port is connected 47 | Serial.println(F("Initializing... ")); 48 | 49 | // start the Ethernet connection: 50 | if (Ethernet.begin(mac) == 0) { 51 | Serial.println(F("Failed to configure Ethernet using DHCP")); 52 | // DHCP failed, so use a fixed IP address: 53 | Ethernet.begin(mac, ip); 54 | Serial.println(F("Fixed IP initialization complete")); 55 | } 56 | else 57 | { 58 | Serial.println(F("Configured Ethernet using DHCP")); 59 | } 60 | 61 | Serial.print(F("device IP is: ")); 62 | Serial.println(Ethernet.localIP()); 63 | 64 | Serial.print(F("gateway IP is: ")); 65 | Serial.println(Ethernet.gatewayIP()); 66 | 67 | Serial.print(F("subnet mask is: ")); 68 | Serial.println(Ethernet.subnetMask()); 69 | 70 | Serial.print(F("DNS is: ")); 71 | Serial.println(Ethernet.dnsServerIP()); 72 | 73 | Serial.print(F("External IP is: ")); 74 | Serial.println(portmap.externalIp()); 75 | 76 | 77 | if (portmap.addPortMap(Ethernet.localIP(),80, 2900)) // add port mapping for user defined port values 78 | //if (portmap.addPortMap(Ethernet.localIP())) // add port mapping for random port values 79 | { 80 | Serial.println(F("Port Mapped successfully!")); 81 | Serial.print(F("Internal Port: ")); 82 | Serial.println(portmap.internalPort()); 83 | Serial.print(F("External Port: ")); 84 | Serial.println(portmap.externalPort()); 85 | } else { 86 | Serial.println(F("unable to map port. Check your router UPNP function is turned on")); 87 | } 88 | 89 | } 90 | 91 | 92 | void loop() { 93 | 94 | delay(4000); 95 | 96 | } 97 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Arduino-Upnp-PortMapping 2 | ======================== 3 | 4 | UPNP based portmapping function for the Arduino 5 | 6 | ## Implementation 7 | 8 | void setup() { 9 | 10 | /* Ethernet and Serial setup code goes here ... */ 11 | 12 | Serial.println(portmap.externalIp()); 13 | 14 | portmap.addPortMap(192.168.0.50, 80, 2900); 15 | 16 | } 17 | 18 | ## Background 19 | 20 | For those of use who create Arduino server type programs using the Ethernet shield this project may help you. Generally there is a limitation when connecting to your Arduino that you need to be on the same LAN. You are not able to connect to the device via the internet. This is because the Arduino is given a private IP address on your internal network, it has no public IP address. 21 | 22 | I had this problem and found there was really only one solution which will allow me to connect to the Arduino remotely. The solution involves using the UPNP functionality which is built into most modern wireless routers. UPNP, and more specifically a sub-protocol called Internet-Gateway-Device allows any device on the local network to add a port mapping to the router, thereby allowing internet resources to access the device by calling the router's WAN (public) IP address and the mapped external port number. The router will pass the packet to the internal IP address and the mapped internal port number. 23 | 24 | The library allows you to: (a) query the external (public) IP address, and (b) add a port mapping entry on the router. Once you have these you can pass them to an external resource which thereafter can connect to the Arduino on this socket (IP address + port number). ISPs dynamically assign public addressses so be aware yours could change anytime. Therefore you could implement this solution with a DDNS solution in order to fix the domain name. 25 | 26 | Unfortunately including this port mapping library uses most of the resources of the Arduino, limiting your ability to do complex sketches. 27 | 28 | ## Installation 29 | 30 | The functionality has been created as a C++ library for Arduino. This library can be added to the Arduino libraries as follows: 31 | 1. Open the 'libraries' folder under your main Arduino folder. 32 | 2. Create a new folder 'PortMapping'. 33 | 3. Clone this repository into that folder 34 | 4. Restart the Arduino IDE so that you can see the example sketch in the examples menu 'Examples > PortMapping > addPortMapping'. 35 | 36 | ## Known Issues 37 | 38 | The UPNP standard is based on the SOAP/XML protocol. XML messages are very verbose, and not ideal for a simple MCU which the Arduino uses. Creating, sending, receiving and parsing SOAP/XML messages uses up most of the Arduino's extremely limited resources. 39 | 40 | This code has been designed and tested with 3 different routers. UPNP implementation across different hardware appears to have slight variances. Due to the simplistic way XML parsing is implemented in this code it is quite possible that this code will not work correctly with other routers. If this is case I am extremely interested in getting feedback and/or code updates to improve the code 41 | 42 | ## Tested Router Models 43 | 44 | 1. TP-LINK TL-WR541G 45 | 2. D-LINK DI-624+A 46 | 3. D-LINK DIR-605 47 | 48 | ** Please send feedback about other models. 49 | 50 | ## Usage example 51 | 52 | See Arduino IDE Example -------------------------------------------------------------------------------- /locateIgd.h: -------------------------------------------------------------------------------- 1 | // UPNP Internet Gateway Device locator - Library v1.0 - August 8th 2012 2 | // Author: Deverick McIntyre 3 | 4 | #ifndef locateIgd_h 5 | #define locateIgd_h 6 | 7 | #include 8 | #include "EthernetUdp.h" 9 | 10 | /* Locate IGD state machine. */ 11 | #define STATE_MSEARCH_START 0 12 | #define STATE_MSEARCH_WAIT_RESPONSE 1 13 | #define STATE_MSEARCH_FOUND 2 14 | 15 | /* UDP IP and port number for M-SEARCH */ 16 | // we have implemented the listener and 17 | // packet send function into a single socket 18 | // so both ports should be the same 19 | #define M_SEARCH_CLIENT_SEND_PORT 1900 20 | #define M_SEARCH_CLIENT_RCVE_PORT 1900 21 | 22 | /* M-Search IP datagram text */ 23 | const prog_char IGD_MSRCH_LINE1[] PROGMEM = "M-SEARCH * HTTP/1.1\r\n"; // 21 chars 24 | const prog_char IGD_MSRCH_LINE2[] PROGMEM = { "HOST: 239.255.255.250:1900\r\n" }; // 28 chars 25 | const prog_char IGD_MSRCH_LINE3[] PROGMEM = { "MAN: \"ssdp:discover\"\r\n" }; // 22 chars 26 | const prog_char IGD_MSRCH_LINE4[] PROGMEM = { "MX: 2\r\n" }; // 7 chars 27 | const prog_char IGD_MSRCH_LINE5a[] PROGMEM = { "ST: urn:schemas-upnp-org:device:" }; //32 chars 28 | const prog_char IGD_MSRCH_LINE5b[] PROGMEM = { "InternetGatewayDevice:1\r\n\r\n" }; // 25 chars 29 | 30 | /* HTTP Request Packet text */ 31 | const prog_char IGD_RQST_LINE1A[] PROGMEM = { "GET " }; 32 | const prog_char IGD_RQST_LINE1B[] PROGMEM = { " HTTP/1.1\r\n\r\n" }; 33 | 34 | const unsigned long IGD_LOCATE_TIMEOUT = 600000; 35 | const unsigned long IGD_RESPONSE_TIMEOUT = 100000; 36 | 37 | // NOTIFY and HTTP packet parsing strings 38 | //const char NOTIFY_PACKET_STRING[7] = "NOTIFY"; 39 | const prog_char NOTIFY_PACKET_STRING[] PROGMEM = "NOTIFY"; 40 | const prog_char HTTP_PACKET_STRING[7] PROGMEM = "HTTP/1"; 41 | const prog_char LINE_FEED[2] PROGMEM = "\n"; 42 | const prog_char CARRIAGE_RETURN[2] PROGMEM = "\r"; 43 | const int ASCII_SPACE = 32; 44 | const prog_char NT_TYPE_ID[4] PROGMEM = "NT:"; 45 | const prog_char ST_TYPE_ID[4] PROGMEM = "ST:"; 46 | const prog_char IGD_STRING_1[32] PROGMEM = "urn:schemas-upnp-org:device:Int"; 47 | const prog_char IGD_STRING_2[21] PROGMEM = "ernetGatewayDevice:1"; 48 | const prog_char LOC_TYPE_ID[4] PROGMEM = "Loc"; 49 | const prog_char LOC_TYPE_ID_2[7] PROGMEM = "ation:"; 50 | const prog_char CAPS_LOC_TYPE_ID[4] PROGMEM = "LOC"; 51 | const prog_char CAPS_LOC_TYPE_ID_2[7] PROGMEM = "ATION:"; 52 | 53 | // XML packet parsing strings 54 | const prog_char SERVICE_TYPE[10] PROGMEM = " line which was wrapping and skipping the next line 55 | const prog_char SERVICE_STRING[32] PROGMEM = "ype>urn:schemas-upnp-org:servic"; 56 | const prog_char SERVICE_STRING_2[20] PROGMEM = "e:WANIPConnection:1"; 57 | const prog_char CONTROL_URL[13] PROGMEM = ""; 58 | const prog_char END_SERVICE[11] PROGMEM = ""; 59 | const prog_char START_XML_TAG[2] PROGMEM = "<"; 60 | 61 | class MSearchClass { 62 | private: 63 | uint32_t _msearchInitialTransactionId; 64 | uint32_t _msearchTransactionId; 65 | uint8_t _msearch_state; 66 | 67 | IPAddress _msearchIgdIp; 68 | EthernetUDP _msearchUdpSocket; 69 | String _msearchIgdControlUrl; 70 | String _msearchLocationUrl; 71 | uint16_t _msearchIgdPort; 72 | EthernetClient _igdClient; 73 | 74 | void reset(); 75 | int find_Igd(); 76 | int send_MSEARCH_MESSAGE(); 77 | int parseMSearchResponse(); 78 | int parseIgdPort(String *locationURL); 79 | int parseControlUrl(String *locationURL, IPAddress igdIp, uint16_t &igdPort); 80 | int parseXmlResponse(); 81 | 82 | public: 83 | IPAddress igdIp(); 84 | uint16_t igdPort(); 85 | String controlUrl(); 86 | int begin(); 87 | 88 | }; 89 | 90 | #endif -------------------------------------------------------------------------------- /portMapping.h: -------------------------------------------------------------------------------- 1 | // UPNP IGD Port Mapping Library v1.0 - August 8th 2012 2 | // Author: Deverick McIntyre 3 | 4 | #ifndef PortMapping_h 5 | #define PortMapping_h 6 | 7 | #include 8 | #include "EthernetUdp.h" 9 | #include "locateIgd.h" 10 | 11 | /* Port Mapping state machine. */ 12 | #define STATE_PORTMAP_START 0 13 | #define STATE_PORTMAP_WAIT_RESPONSE 1 14 | #define STATE_PORTMAP_CONFIRMED 2 15 | 16 | /* Default Port values */ 17 | #define RAND_PORT_RANGE_MIN 50000 18 | #define RAND_PORT_RANGE_MAX 59000 19 | 20 | const unsigned long PM_TOTAL_TIMEOUT = 300000; 21 | const unsigned long PM_RESPONSE_TIMEOUT = 50000; 22 | 23 | /* HTTP add Port Map Request Packet text */ 24 | const prog_char PM_HEADER1A[] PROGMEM = "POST "; 25 | const prog_char PM_HEADER1B[] PROGMEM = " HTTP/1.1\r\nSOAPACTION: \"urn:schemas-upnp-org:service:WANIPConnection:1#AddPortMapping\"\r\nCONTENT-TYPE: text/xml; charset=\"utf-8\"\r\nHOST: "; 26 | const prog_char PM_HEADER1C[] PROGMEM = "\r\nContent-Length: 702\r\n\r\n"; 27 | 28 | const prog_char PM_BODY1[] PROGMEM = "\r\n"; 29 | const prog_char PM_BODY2[] PROGMEM = "\r\n"; 30 | const prog_char PM_BODY3[] PROGMEM = " \r\n"; 31 | const prog_char PM_BODY4[] PROGMEM = " \r\n"; 32 | const prog_char PM_BODY5[] PROGMEM = " \r\n"; 33 | const prog_char PM_BODY6A[] PROGMEM = " "; 34 | const prog_char PM_BODY6B[] PROGMEM = "\r\n"; 35 | const prog_char PM_BODY7[] PROGMEM = " TCP\r\n"; 36 | const prog_char PM_BODY8A[] PROGMEM = " "; 37 | const prog_char PM_BODY8B[] PROGMEM = "\r\n"; 38 | const prog_char PM_BODY9A[] PROGMEM = " "; 39 | const prog_char PM_BODY9B[] PROGMEM = "\r\n"; 40 | const prog_char PM_BODY10[] PROGMEM = " 1\r\n"; 41 | const prog_char PM_BODY11[] PROGMEM = " Arduino\r\n"; 42 | const prog_char PM_BODY12[] PROGMEM = " 0\r\n"; 43 | const prog_char PM_BODY13[] PROGMEM = " \r\n"; 44 | const prog_char PM_BODY14[] PROGMEM = " \r\n"; 45 | const prog_char PM_BODY15[] PROGMEM = ""; 46 | 47 | 48 | /* HTTP get external IP request Packet text */ 49 | const prog_char EXTIP_HEADER1A[] PROGMEM = "POST "; 50 | const prog_char EXTIP_HEADER1B[] PROGMEM = " HTTP/1.1\r\nSOAPACTION: \"urn:schemas-upnp-org:service:WANIPConnection:1#GetExternalIPAddress\"\r\nCONTENT-TYPE: text/xml; charset=\"utf-8\"\r\nHOST: "; 51 | const prog_char EXTIP_HEADER1C[] PROGMEM = "\r\nContent-Length: 297\r\n\r\n"; 52 | const prog_char EXTIP_BODY1[] PROGMEM = "\r\n"; 53 | const prog_char EXTIP_BODY2[] PROGMEM = "\r\n"; 54 | const prog_char EXTIP_BODY3[] PROGMEM = " \r\n"; 55 | const prog_char EXTIP_BODY4[] PROGMEM = " \r\n"; 56 | const prog_char EXTIP_BODY5[] PROGMEM = " \r\n"; 57 | const prog_char EXTIP_BODY6[] PROGMEM = ""; 58 | 59 | // NOTIFY and HTTP packet parsing strings 60 | const prog_char HTTP_RESPONSE[13] PROGMEM = "HTTP/1.1 200"; 61 | const prog_char XML_TAG_OPEN[2] PROGMEM = "<"; 62 | const prog_char XML_TAG_EXTIP[22] PROGMEM = "NewExternalIPAddress>"; 63 | 64 | class PortMapClass { 65 | private: 66 | uint8_t _portmap_state; 67 | MSearchClass msearch; 68 | uint16_t _portmapInternalPort; 69 | uint16_t _portmapExternalPort; 70 | IPAddress _externalIp; 71 | uint16_t _portmapRandomPort; 72 | EthernetClient _apmClient; 73 | IPAddress _igdIp; 74 | uint16_t _igdPort; 75 | String _igdControlUrl; 76 | 77 | void reset(); 78 | int addPortMapRequest(IPAddress myIP, uint16_t requestedIntPort, uint16_t requestedExtPort); 79 | int parsePortMapResponse(uint16_t requestedIntPort, uint16_t requestedExtPort); 80 | int getExternalIpRequest(); 81 | int parseExternalIpResponse(); 82 | IPAddress igdIp(); 83 | uint16_t igdPort(); 84 | String igdControlUrl(); 85 | int getIgdParams(); 86 | 87 | public: 88 | int addPortMap(IPAddress myIP); 89 | int addPortMap(IPAddress myIP, uint16_t requestedIntPort, uint16_t requestedExtPort); 90 | uint16_t internalPort(); 91 | uint16_t externalPort(); 92 | IPAddress externalIp(); 93 | 94 | }; 95 | 96 | #endif -------------------------------------------------------------------------------- /portMapping.cpp: -------------------------------------------------------------------------------- 1 | // UPNP IGD Port Mapping Library v1.0 - August 8th 2012 2 | // Author: Deverick McIntyre 3 | 4 | #include "portMapping.h" 5 | 6 | 7 | void PortMapClass::reset() 8 | { 9 | _portmap_state = 0; 10 | _portmapInternalPort = 0; 11 | _portmapExternalPort = 0; 12 | } 13 | 14 | int PortMapClass::addPortMap(IPAddress myIP) 15 | { 16 | unsigned long responseStartTime = millis(); 17 | uint16_t randomPort; 18 | 19 | while ((millis() - responseStartTime) < PM_RESPONSE_TIMEOUT) //keep trying with different random port numbers until success or time runs out 20 | { 21 | randomSeed(millis()); 22 | randomPort = random(RAND_PORT_RANGE_MIN, RAND_PORT_RANGE_MAX); 23 | if (addPortMap(myIP, randomPort, randomPort)) // if add port mapping was successful then exit 24 | { 25 | return 1; 26 | } 27 | delay(10000); 28 | } 29 | return 0; //timed out before successful port mapping so fail 30 | } 31 | 32 | int PortMapClass::addPortMap(IPAddress myIP, uint16_t requestedIntPort, uint16_t requestedExtPort) 33 | { 34 | reset(); 35 | 36 | return addPortMapRequest(myIP, requestedIntPort, requestedExtPort); 37 | } 38 | 39 | int PortMapClass::addPortMapRequest(IPAddress myIP, uint16_t requestedIntPort, uint16_t requestedExtPort) 40 | { 41 | 42 | if (_apmClient.connect(igdIp(), igdPort())) 43 | { 44 | char * buffer = (char *) malloc (448); 45 | memset(buffer, 0, 448); 46 | char * smallbuffer = (char *) malloc (8); 47 | memset(smallbuffer, 0,8); 48 | 49 | strcpy_P(buffer, PM_HEADER1A); 50 | 51 | // convert control URL from string to char array and append to buffer contents 52 | String controlUrlString = igdControlUrl(); 53 | char controlUrlStringArray[controlUrlString.length()+1]; 54 | controlUrlString.toCharArray(controlUrlStringArray, sizeof(controlUrlStringArray)); 55 | strcat(buffer, controlUrlStringArray); 56 | 57 | strcat_P(buffer, PM_HEADER1B); 58 | 59 | // convert igd IP address to char array and append to buffer contents 60 | IPAddress igdIpAddress = igdIp(); 61 | strcat(buffer, itoa(igdIpAddress[0], smallbuffer, 10)); 62 | memset(smallbuffer, 0,8); 63 | strcat(buffer, "."); 64 | strcat(buffer, itoa(igdIpAddress[1], smallbuffer, 10)); 65 | memset(smallbuffer, 0,8); 66 | strcat(buffer, "."); 67 | strcat(buffer, itoa(igdIpAddress[2], smallbuffer, 10)); 68 | memset(smallbuffer, 0,8); 69 | strcat(buffer, "."); 70 | strcat(buffer, itoa(igdIpAddress[3], smallbuffer, 10)); 71 | memset(smallbuffer, 0,8); 72 | strcat(buffer, ":"); 73 | 74 | // convert igd port number to char array and append 75 | uint16_t igdPortNumber = igdPort(); 76 | strcat(buffer, itoa(igdPortNumber, smallbuffer, 10)); 77 | memset(smallbuffer, 0,8); 78 | 79 | strcat_P(buffer, PM_HEADER1C); 80 | 81 | strcat_P(buffer, PM_BODY1); 82 | 83 | strcat_P(buffer, PM_BODY2); 84 | 85 | strcat_P(buffer, PM_BODY3); 86 | 87 | strcat_P(buffer, PM_BODY4); 88 | 89 | strcat_P(buffer, PM_BODY5); 90 | 91 | strcat_P(buffer, PM_BODY6A); 92 | 93 | strcat(buffer, itoa(requestedExtPort, smallbuffer, 10)); 94 | memset(smallbuffer, 0,8); 95 | 96 | strcat_P(buffer, PM_BODY6B); 97 | 98 | strcat_P(buffer, PM_BODY7); 99 | 100 | strcat_P(buffer, PM_BODY8A); 101 | 102 | strcat(buffer, itoa(requestedIntPort, smallbuffer, 10)); 103 | memset(smallbuffer, 0,8); 104 | 105 | strcat_P(buffer, PM_BODY8B); 106 | 107 | strcat_P(buffer, PM_BODY9A); 108 | 109 | strcat(buffer, itoa(myIP[0], smallbuffer, 10)); 110 | memset(smallbuffer, 0,8); 111 | strcat(buffer, "."); 112 | strcat(buffer, itoa(myIP[1], smallbuffer, 10)); 113 | memset(smallbuffer, 0,8); 114 | strcat(buffer, "."); 115 | strcat(buffer, itoa(myIP[2], smallbuffer, 10)); 116 | memset(smallbuffer, 0,8); 117 | strcat(buffer, "."); 118 | strcat(buffer, itoa(myIP[3], smallbuffer, 10)); 119 | memset(smallbuffer, 0,8); 120 | 121 | strcat_P(buffer, PM_BODY9B); 122 | _apmClient.print(buffer); 123 | memset(buffer, 0,448); 124 | 125 | strcpy_P(buffer, PM_BODY10); 126 | 127 | strcat_P(buffer, PM_BODY11); 128 | 129 | strcat_P(buffer, PM_BODY12); 130 | 131 | strcat_P(buffer, PM_BODY13); 132 | 133 | strcat_P(buffer, PM_BODY14); 134 | 135 | strcat_P(buffer, PM_BODY15); 136 | _apmClient.print(buffer); 137 | 138 | free(buffer); 139 | free(smallbuffer); 140 | 141 | return parsePortMapResponse(requestedIntPort, requestedExtPort); 142 | 143 | } else { 144 | // if you didn't get a connection to the server: 145 | _apmClient.stop(); 146 | return 0; 147 | } 148 | } 149 | 150 | int PortMapClass::parsePortMapResponse(uint16_t requestedIntPort, uint16_t requestedExtPort) 151 | { 152 | // set variable intial states, and create local variables 153 | char * buffer = (char *) malloc (32); 154 | memset(buffer, 0, 32); 155 | char c; 156 | unsigned long responseStartTime = millis(); 157 | 158 | while (_apmClient.connected()) 159 | { 160 | if (_apmClient.available()) 161 | { 162 | memset(buffer, 0, 32); 163 | _apmClient.readBytes(buffer, 12); 164 | if (strncmp_P(buffer, HTTP_RESPONSE, 12) == 0) // if packet is 200 OK response then store port numbers 165 | { 166 | _portmapInternalPort = requestedIntPort; 167 | _portmapExternalPort = requestedExtPort; 168 | _apmClient.flush(); 169 | _apmClient.stop(); 170 | free(buffer); 171 | return 1; 172 | } else { // did not receive a 200 OK HTTP response so cleanup and return a fail 173 | while (_apmClient.available()) 174 | { 175 | c = _apmClient.read(); //read the next character 176 | Serial.print(c); 177 | } 178 | _apmClient.flush(); 179 | _apmClient.stop(); 180 | free(buffer); 181 | return 0; 182 | } 183 | } 184 | if((millis() - responseStartTime) > PM_RESPONSE_TIMEOUT) 185 | { 186 | //Serial.println(F("ran out of time!")); 187 | _apmClient.flush(); 188 | _apmClient.stop(); 189 | free(buffer); 190 | return 0; 191 | } 192 | } 193 | _apmClient.flush(); 194 | _apmClient.stop(); 195 | free(buffer); 196 | return 0; 197 | } 198 | 199 | uint16_t PortMapClass::internalPort() 200 | { 201 | // returns the internal port if port map successful, else zero 202 | return _portmapInternalPort; 203 | } 204 | 205 | uint16_t PortMapClass::externalPort() 206 | { 207 | // returns the external port if port map successful, else zero 208 | return _portmapExternalPort; 209 | } 210 | 211 | IPAddress PortMapClass::externalIp() 212 | { 213 | if (_externalIp[1] != 0) 214 | { 215 | // if we already have retrieved the external Ip then return it 216 | return _externalIp; 217 | 218 | } else { 219 | 220 | getExternalIpRequest(); 221 | } 222 | // returns the external Ip if check successful, else zero Ip 223 | return _externalIp; 224 | } 225 | 226 | int PortMapClass::getExternalIpRequest() 227 | { 228 | 229 | if (_apmClient.connect(igdIp(), igdPort())) 230 | { 231 | char * buffer = (char *) malloc (256); 232 | memset(buffer, 0, 256); 233 | char * smallbuffer = (char *) malloc (8); 234 | memset(smallbuffer, 0,8); 235 | 236 | // Make a HTTP request: 237 | 238 | strcpy_P(buffer, EXTIP_HEADER1A); 239 | 240 | // convert control URL from string to char array and append to buffer contents 241 | String controlUrlString = igdControlUrl(); 242 | char controlUrlStringArray[controlUrlString.length()+1]; 243 | controlUrlString.toCharArray(controlUrlStringArray, sizeof(controlUrlStringArray)); 244 | strcat(buffer, controlUrlStringArray); 245 | 246 | strcat_P(buffer, EXTIP_HEADER1B); 247 | 248 | // convert igd IP address to char array and append to buffer contents 249 | IPAddress igdIpAddress = igdIp(); 250 | strcat(buffer, itoa(igdIpAddress[0], smallbuffer, 10)); 251 | memset(smallbuffer, 0,8); 252 | strcat(buffer, "."); 253 | strcat(buffer, itoa(igdIpAddress[1], smallbuffer, 10)); 254 | memset(smallbuffer, 0,8); 255 | strcat(buffer, "."); 256 | strcat(buffer, itoa(igdIpAddress[2], smallbuffer, 10)); 257 | memset(smallbuffer, 0,8); 258 | strcat(buffer, "."); 259 | strcat(buffer, itoa(igdIpAddress[3], smallbuffer, 10)); 260 | memset(smallbuffer, 0,8); 261 | strcat(buffer, ":"); 262 | 263 | // convert igd port number to char array and append 264 | uint16_t igdPortNumber = igdPort(); 265 | strcat(buffer, itoa(igdPortNumber, smallbuffer, 10)); 266 | memset(smallbuffer, 0,8); 267 | 268 | strcat_P(buffer, EXTIP_HEADER1C); 269 | 270 | strcat_P(buffer, EXTIP_BODY1); 271 | 272 | strcat_P(buffer, EXTIP_BODY2); 273 | 274 | strcat_P(buffer, EXTIP_BODY3); 275 | 276 | strcat_P(buffer, EXTIP_BODY4); 277 | 278 | strcat_P(buffer, EXTIP_BODY5); 279 | 280 | strcat_P(buffer, EXTIP_BODY6); 281 | _apmClient.print(buffer); 282 | 283 | free(buffer); 284 | free(smallbuffer); 285 | 286 | return parseExternalIpResponse(); 287 | } else { 288 | // if you didn't get a connection to the server: 289 | _apmClient.stop(); 290 | return 0; 291 | } 292 | } 293 | 294 | int PortMapClass::parseExternalIpResponse() 295 | { 296 | // set variable intial states, and create local variables 297 | char * buffer = (char *) malloc (32); 298 | memset(buffer, 0, 32); 299 | char c; 300 | unsigned long responseStartTime = millis(); 301 | 302 | while (_apmClient.connected()) 303 | { 304 | if (_apmClient.available()) 305 | { 306 | memset(buffer, 0, 32); 307 | _apmClient.readBytes(buffer, 12); 308 | if (strncmp_P(buffer, HTTP_RESPONSE, 12) == 0) // if packet is 200 OK response then continue 309 | { 310 | // setup inner loop to iterate thru packet contents to find external IP tag 311 | while (_apmClient.connected()) 312 | { 313 | c = _apmClient.read(); 314 | if (strncmp_P(&c, XML_TAG_OPEN, 1) == 0) // if character is tag start then continue 315 | { 316 | memset(buffer, 0, 32); 317 | _apmClient.readBytes(buffer, 21); 318 | if (strncmp_P(buffer, XML_TAG_EXTIP, 21) == 0) // if string is external IP tag start then continue 319 | { 320 | //copy the IP address into a string variable for parsing 321 | String ipString; 322 | c = _apmClient.read(); 323 | while (strncmp_P(&c, XML_TAG_OPEN, 1) != 0) 324 | { 325 | ipString += c; 326 | c = _apmClient.read(); 327 | } 328 | // next parse the string into separate IP address octets and store in local variable 329 | int firstDot = ipString.indexOf('.'); // get the locations of the dots in the IP string 330 | int secondDot = ipString.indexOf('.', firstDot + 1); 331 | int thirdDot = ipString.indexOf('.', secondDot + 1); 332 | if (firstDot == -1 || secondDot == -1 || thirdDot == -1) // if we cant find 3 dots then IP address invalid so error 333 | { 334 | return 0; 335 | } 336 | String firstOctet = ipString.substring(0, firstDot); // next substring each octet out from between dots 337 | String secondOctet = ipString.substring(firstDot + 1, secondDot); 338 | String thirdOctet = ipString.substring(secondDot + 1, thirdDot); 339 | String fourthOctet = ipString.substring(thirdDot + 1); 340 | char firstOctetArray[firstOctet.length()+1]; // next instantiate char arrays for conversion 341 | char secondOctetArray[secondOctet.length()+1]; 342 | char thirdOctetArray[thirdOctet.length()+1]; 343 | char fourthOctetArray[fourthOctet.length()+1]; 344 | firstOctet.toCharArray(firstOctetArray, sizeof(firstOctetArray)); //convert strings to char arrays 345 | secondOctet.toCharArray(secondOctetArray, sizeof(secondOctetArray)); 346 | thirdOctet.toCharArray(thirdOctetArray, sizeof(thirdOctetArray)); 347 | fourthOctet.toCharArray(fourthOctetArray, sizeof(fourthOctetArray)); 348 | _externalIp[0] = atoi(firstOctetArray); // convert char arrays to integers and assign to class variable 349 | _externalIp[1] = atoi(secondOctetArray); 350 | _externalIp[2] = atoi(thirdOctetArray); 351 | _externalIp[3] = atoi(fourthOctetArray); 352 | _apmClient.flush(); 353 | _apmClient.stop(); 354 | free(buffer); 355 | return 1; 356 | } 357 | } 358 | } 359 | // if we dropped out to here we did not find the external IP so error 360 | return 0; 361 | 362 | } else { // did not receive a 200 OK HTTP response so cleanup and return a fail 363 | _apmClient.flush(); 364 | _apmClient.stop(); 365 | free(buffer); 366 | return 0; 367 | } 368 | } 369 | if((millis() - responseStartTime) > PM_RESPONSE_TIMEOUT) 370 | { 371 | //Serial.println(F("ran out of time!")); 372 | _apmClient.flush(); 373 | _apmClient.stop(); 374 | free(buffer); 375 | return 0; 376 | } 377 | } 378 | _apmClient.flush(); 379 | _apmClient.stop(); 380 | free(buffer); 381 | return 0; 382 | } 383 | 384 | IPAddress PortMapClass::igdIp() 385 | { 386 | if (_igdIp[0] != 0) 387 | { 388 | // if we already have igd IP then return it 389 | return _igdIp; 390 | 391 | } else { 392 | if (getIgdParams() == 1) 393 | { 394 | return _igdIp; 395 | } 396 | } 397 | } 398 | 399 | uint16_t PortMapClass::igdPort() 400 | { 401 | 402 | if (_igdPort != 0) 403 | { 404 | // if we already have retrieved the igd Port then return it 405 | return _igdPort; 406 | 407 | } else { 408 | if (getIgdParams() == 1) 409 | { 410 | return _igdPort; 411 | } 412 | } 413 | } 414 | 415 | String PortMapClass::igdControlUrl() 416 | { 417 | 418 | if (_igdControlUrl != "") 419 | { 420 | // if we already have retrieved the igd control URL then return it 421 | return _igdControlUrl; 422 | 423 | } else { 424 | if (getIgdParams() == 1) 425 | { 426 | return _igdControlUrl; 427 | } 428 | } 429 | } 430 | 431 | int PortMapClass::getIgdParams() 432 | { 433 | if (msearch.begin() != 0) 434 | { 435 | _igdIp = msearch.igdIp(); 436 | _igdPort = msearch.igdPort(); 437 | _igdControlUrl = msearch.controlUrl(); 438 | return 1; 439 | 440 | } else { 441 | return 0; 442 | } 443 | 444 | } -------------------------------------------------------------------------------- /locateIgd.cpp: -------------------------------------------------------------------------------- 1 | // UPNP Internet Gateway Device locator - Library v1.0 - August 8th 2012 2 | // Author: Deverick McIntyre 3 | 4 | 5 | #include "locateIgd.h" 6 | #include "Arduino.h" 7 | 8 | int MSearchClass::begin() 9 | { 10 | reset(); 11 | return find_Igd(); 12 | } 13 | 14 | void MSearchClass::reset() 15 | { 16 | // zero out or null all variables 17 | _msearch_state = 0; 18 | _msearchIgdIp = (0, 0, 0, 0); 19 | _msearchIgdControlUrl = ""; 20 | _msearchIgdPort = 0; 21 | 22 | } 23 | 24 | IPAddress MSearchClass::igdIp() 25 | { 26 | return _msearchIgdIp; 27 | } 28 | 29 | uint16_t MSearchClass::igdPort() 30 | { 31 | return _msearchIgdPort; 32 | } 33 | 34 | String MSearchClass::controlUrl() 35 | { 36 | return _msearchIgdControlUrl; 37 | } 38 | 39 | int MSearchClass::find_Igd() 40 | { 41 | // Pick an initial transaction ID 42 | _msearchTransactionId = random(1UL, 2000UL); 43 | _msearchInitialTransactionId = _msearchTransactionId; 44 | 45 | if (_msearchUdpSocket.begin(M_SEARCH_CLIENT_RCVE_PORT) == 0) 46 | { 47 | // Couldn't get a socket 48 | return 0; 49 | } 50 | int result = 0; 51 | 52 | unsigned long startTime = millis(); 53 | 54 | while(_msearch_state != STATE_MSEARCH_FOUND) 55 | { 56 | if (_msearch_state == STATE_MSEARCH_START) 57 | { 58 | if (send_MSEARCH_MESSAGE() == 1) 59 | { 60 | _msearchTransactionId++; 61 | _msearch_state = STATE_MSEARCH_WAIT_RESPONSE; 62 | } 63 | } 64 | 65 | if (_msearch_state == STATE_MSEARCH_WAIT_RESPONSE) 66 | { 67 | _msearchTransactionId++; 68 | if (parseMSearchResponse() == 1) 69 | { 70 | _msearch_state == STATE_MSEARCH_FOUND; 71 | result = 1; 72 | } 73 | break; 74 | } 75 | 76 | if(result != 1 && ((millis() - startTime) > IGD_LOCATE_TIMEOUT)) 77 | break; 78 | } 79 | 80 | // We're done with the socket now 81 | _msearchUdpSocket.stop(); 82 | _msearchTransactionId++; 83 | 84 | return result; 85 | } 86 | 87 | int MSearchClass::send_MSEARCH_MESSAGE() 88 | { 89 | static const IPAddress ms_bcast_addr( 239, 255, 255, 250 ); // M-SEARCH Broadcast address 90 | 91 | if (-1 == _msearchUdpSocket.beginPacket(ms_bcast_addr, M_SEARCH_CLIENT_SEND_PORT)) 92 | { 93 | return 0; 94 | } 95 | char * buffer = (char *) malloc (32); 96 | memset(buffer, 0, 32); 97 | 98 | strcpy_P(buffer, IGD_MSRCH_LINE1); 99 | _msearchUdpSocket.print(buffer); 100 | memset(buffer, 0, 32); // clear local buffer 101 | 102 | strcpy_P(buffer, IGD_MSRCH_LINE2); 103 | _msearchUdpSocket.print(buffer); 104 | memset(buffer, 0, 32); // clear local buffer 105 | 106 | strcpy_P(buffer, IGD_MSRCH_LINE3); 107 | _msearchUdpSocket.print(buffer); 108 | memset(buffer, 0, 32); // clear local buffer 109 | 110 | strcpy_P(buffer, IGD_MSRCH_LINE4); 111 | _msearchUdpSocket.print(buffer); 112 | memset(buffer, 0, 32); // clear local buffer 113 | 114 | strcpy_P(buffer, IGD_MSRCH_LINE5a); 115 | _msearchUdpSocket.print(buffer); 116 | memset(buffer, 0, 32); // clear local buffer 117 | 118 | strcpy_P(buffer, IGD_MSRCH_LINE5b); 119 | _msearchUdpSocket.print(buffer); 120 | 121 | 122 | free(buffer); 123 | 124 | int sendResult = _msearchUdpSocket.endPacket(); 125 | return sendResult; 126 | 127 | } 128 | // *** Due to differing implementations of discovery for so called upnp enabled devices, we look for two different packet types to verify 129 | // *** a device is an IGD. So, strictly speaking we should only accept the NOTIFY datagram, but in this implementation we also accept the HTTP 130 | // *** datagram with the correct payload. 131 | // Below is an example structure for the UDP NOTIFY datagram from a root device which has the 'InternetGatewayDevice' notification type 132 | // Following is the logic for parsing a datagram to confirm it is an IGD NOTIFY datagram 133 | // first we check first six characters is 'NOTIFY' 134 | // then we read through characters to each line feed character 135 | // after each line feed character we look for a 'NT:' or 'Loc' string 136 | // if 'NT:' string found then remove any trailing white space characters 137 | // remaining string on that line should be 'urn:schemas-upnp-org:device:InternetGatewayDevice:1' 138 | // if 'Loc' string found then check if 'ation:' string follows 139 | // then remove any trailing white spaces after : 140 | // then move the rest of the line into our string variable 141 | //**************** 142 | // NOTIFY * HTTP/1.1 143 | // Host:239.255.255.250:1900 144 | // Cache-Control:max-age=180 145 | // Location:http://192.168.0.1:52869/picsdesc.xml 146 | // Server:OS 1.0 UPnP/1.0 Realtek/V1.3 147 | // NT:urn:schemas-upnp-org:device:InternetGatewayDevice:1 148 | // USN:uuid:12342409-1234-1234-5678-ee1234cc5678::urn:schemas-upnp-org:device:InternetGatewayDevice:1 149 | // NTS:ssdp:alive 150 | //********************* 151 | // 152 | // Below is an example structure for the HTTP response datagram from a root device which has the 'InternetGatewayDevice' notification type 153 | // Following is the logic for parsing a datagram to confirm it is an IGD HTTP response datagram 154 | // first we check first six characters is 'HTTP/1' 155 | // then we read through characters to each line feed character 156 | // after each line feed character we look for a 'ST:' string 157 | // remove any space characters after the 'ST:' string 158 | // remaining string on that line should be 'urn:schemas-upnp-org:device:InternetGatewayDevice:1' 159 | //********************* 160 | // HTTP/1.1 200 OK 161 | // Cache-Control: max-age=180 162 | // ST: urn:schemas-upnp-org:device:InternetGatewayDevice:1 163 | // USN: uuid:12342409-1234-1234-5678-ee1234cc5678::urn:schemas-upnp-org:device:InternetGatewayDevice:1 164 | // EXT: 165 | // Server: OS 1.0 UPnP/1.0 Realtek/V1.3 166 | // Location: http://192.168.0.1:52869/picsdesc.xml 167 | //********************* 168 | 169 | int MSearchClass::parseMSearchResponse() 170 | { 171 | // set variable intial states, and create local variables 172 | char * buffer = (char *) malloc (32); 173 | memset(buffer, 0, 32); 174 | char c; 175 | unsigned long responseStartTime = millis(); 176 | 177 | int type_flag = 0; 178 | int result = 0; 179 | 180 | _msearchIgdControlUrl = ""; 181 | _msearchIgdPort = 0; 182 | 183 | while ((millis() - responseStartTime) < IGD_RESPONSE_TIMEOUT) 184 | { 185 | // loop will return to here after each packet to wait for next packet. 186 | while(_msearchUdpSocket.parsePacket() <= 0) 187 | { 188 | if((millis() - responseStartTime) > IGD_RESPONSE_TIMEOUT) 189 | { 190 | //Serial.println(F("ran out of time!")); 191 | _msearchUdpSocket.stop(); 192 | free(buffer); 193 | return 0; 194 | } 195 | delay(40); 196 | } 197 | 198 | while (_msearchUdpSocket.available()) 199 | { 200 | // first six characters of UPD frame should be 'NOTIFY' or 'HTTP/1' 201 | memset(buffer, 0, 32); 202 | _msearchUdpSocket.read(buffer, 6); 203 | /* 204 | NOTIFY and HTTP/1 DATAGRAM PROCESSING 205 | */ 206 | if ((strncmp_P(buffer, NOTIFY_PACKET_STRING, 6) == 0) || (strncmp_P(buffer, HTTP_PACKET_STRING, 6) == 0)) 207 | { 208 | while (_msearchUdpSocket.available()) 209 | { 210 | c = _msearchUdpSocket.read(); 211 | while (strncmp_P(&c, LINE_FEED, 1) != 0) // iterate through the characters until we hit a LF then drop out 212 | { 213 | c = _msearchUdpSocket.read(); 214 | } 215 | memset(buffer, 0, 32); 216 | _msearchUdpSocket.read(buffer, 3); 217 | if ((strncmp_P(buffer, NT_TYPE_ID, 3) == 0) || (strncmp_P(buffer, ST_TYPE_ID, 3) == 0))// if new line starts with 'NT:' then continue checking 218 | { 219 | // first strip out any spaces 220 | while (_msearchUdpSocket.peek() == ASCII_SPACE) 221 | { 222 | _msearchUdpSocket.read(); //discard white space character 223 | } 224 | memset(buffer, 0, 32); 225 | _msearchUdpSocket.read(buffer, 31); 226 | if (strncmp_P(buffer, IGD_STRING_1, 31) == 0) // check the first part of string is correct 227 | { 228 | memset(buffer, 0, 32); 229 | _msearchUdpSocket.read(buffer, 20); 230 | if (strncmp_P(buffer, IGD_STRING_2, 20) == 0) // check the second part of string is correct 231 | { 232 | type_flag = 1; 233 | } 234 | } 235 | // if new line starts with 'Loc' or 'LOC' then continue checking 236 | } else if ((strncmp_P(buffer, LOC_TYPE_ID, 3) == 0) || (strncmp_P(buffer, CAPS_LOC_TYPE_ID, 3) == 0)) 237 | { 238 | memset(buffer, 0, 32); 239 | _msearchUdpSocket.read(buffer, 6); 240 | // if rest of word is 'ation:' or 'ATION:' then continue checking 241 | if ((strncmp_P(buffer, LOC_TYPE_ID_2, 6) == 0) || (strncmp_P(buffer, CAPS_LOC_TYPE_ID_2, 6) == 0)) 242 | { 243 | // first strip out any spaces 244 | while (_msearchUdpSocket.peek() == ASCII_SPACE) 245 | { 246 | _msearchUdpSocket.read(); //discard white space character 247 | } 248 | c = _msearchUdpSocket.read(); //read the next character 249 | while (strncmp_P(&c, CARRIAGE_RETURN, 1) != 0) // if we find a carriage return it means the URL and line have ended 250 | { 251 | _msearchLocationUrl += c; 252 | c = _msearchUdpSocket.read(); //read the next character and append to the string 253 | } 254 | } 255 | } 256 | 257 | // if we have the location URL AND the packet is of the type we need then we can stop processing packets. 258 | if (_msearchLocationUrl.length() && type_flag) 259 | { 260 | // if we are able to parse out the port number then try to parse out the controlURL 261 | if (parseIgdPort(&_msearchLocationUrl)) // if we are able to find 262 | { 263 | _msearchIgdIp = _msearchUdpSocket.remoteIP(); 264 | // finally if we are able to parse out the control URL then return success 265 | if (parseControlUrl(&_msearchLocationUrl, _msearchIgdIp, _msearchIgdPort)) { 266 | _msearchUdpSocket.stop(); 267 | free(buffer); 268 | return 1; 269 | } else { 270 | // unable to parse out the control URL. return error condition 271 | _msearchUdpSocket.stop(); 272 | free(buffer); 273 | return 0; 274 | } 275 | } else { 276 | // unable to parse out the port number. return error condition 277 | _msearchUdpSocket.stop(); 278 | free(buffer); 279 | return 0; 280 | } 281 | 282 | } 283 | 284 | } 285 | 286 | } else { 287 | // if we are here it is because this packet is not what we are looking for 288 | // need to discard packet 289 | 290 | // skip to the end of the packet 291 | _msearchUdpSocket.flush(); 292 | 293 | //Serial.print(F("packet found but not notify or HTTP type")); 294 | //while (_msearchUdpSocket.available()) 295 | //{ 296 | // char c = _msearchUdpSocket.read(); 297 | // Serial.print(c); 298 | //} 299 | } 300 | // if we are here it is because a packet failed validation. need to reset variables and wait for next packet. 301 | _msearchIgdControlUrl = ""; 302 | _msearchLocationUrl = ""; 303 | _msearchIgdPort = 0; 304 | } 305 | 306 | } 307 | _msearchUdpSocket.stop(); 308 | free(buffer); 309 | return 0; 310 | } 311 | 312 | int MSearchClass::parseIgdPort(String *locationURL) 313 | { 314 | String tempString = *locationURL; 315 | int lastColon = tempString.lastIndexOf(':'); 316 | int firstSlash = tempString.indexOf('/', lastColon); 317 | tempString = tempString.substring(lastColon+1, firstSlash); 318 | char charArray[tempString.length()+1]; 319 | tempString.toCharArray(charArray, sizeof(charArray)); 320 | _msearchIgdPort = atof(charArray); 321 | if (_msearchIgdPort) { 322 | return 1; 323 | } else { 324 | return 0; 325 | } 326 | } 327 | 328 | int MSearchClass::parseControlUrl(String *locationURL, IPAddress igdIp, uint16_t &igdPort) 329 | { 330 | String locationUrlString = *locationURL; 331 | int lastColon = locationUrlString.lastIndexOf(':'); 332 | int firstSlash = locationUrlString.indexOf('/', lastColon); 333 | locationUrlString = locationUrlString.substring(firstSlash); 334 | 335 | char locationUrlArray[locationUrlString.length()+1]; 336 | locationUrlString.toCharArray(locationUrlArray, locationUrlString.length()+1); 337 | 338 | if (_igdClient.connect(igdIp, igdPort)) { 339 | 340 | char * buffer = (char *) malloc (32); 341 | memset(buffer, 0, 32); 342 | // Make a HTTP request: 343 | strcpy_P(buffer, IGD_RQST_LINE1A); 344 | _igdClient.print(buffer); 345 | memset(buffer, 0, 32); 346 | 347 | _igdClient.write((uint8_t *)locationUrlArray, locationUrlString.length()); 348 | 349 | strcpy_P(buffer, IGD_RQST_LINE1B); 350 | _igdClient.print(buffer); 351 | 352 | free(buffer); 353 | return parseXmlResponse(); 354 | } 355 | else { 356 | // if you didn't get a connection to the server: 357 | _igdClient.stop(); 358 | return 0; 359 | } 360 | } 361 | 362 | int MSearchClass::parseXmlResponse() 363 | { 364 | // set variable intial states, and create local variables 365 | char * buffer = (char *) malloc (32); 366 | memset(buffer, 0, 32); 367 | char c; 368 | unsigned long responseStartTime = millis(); 369 | 370 | while (_igdClient.connected()) 371 | { 372 | if (_igdClient.available()) 373 | { 374 | // first strip out any spaces 375 | while (_igdClient.peek() == ASCII_SPACE) 376 | { 377 | _igdClient.read(); //discard white space character 378 | } 379 | memset(buffer, 0, 32); 380 | _igdClient.readBytes(buffer, 9); 381 | if (strncmp_P(buffer, SERVICE_TYPE, 9) == 0) // if new line starts with ' however we should not get this far before 393 | // finding the control URL tag. not finding control URL tag and getting to is an error condition 394 | while (strncmp_P(buffer, END_SERVICE, 10) != 0) 395 | { 396 | c = _igdClient.read(); 397 | while (strncmp_P(&c, LINE_FEED, 1) != 0) // iterate through the characters until we hit a LF then drop out 398 | { 399 | c = _igdClient.read(); 400 | } 401 | // strip out any spaces at start of new line 402 | while (_igdClient.peek() == ASCII_SPACE) 403 | { 404 | _igdClient.read(); //discard white space character 405 | } 406 | memset(buffer, 0, 32); 407 | _igdClient.readBytes(buffer, 12); 408 | if (strncmp_P(buffer, CONTROL_URL, 12) == 0) // if new line starts with '' then continue checking 409 | { 410 | c = _igdClient.read(); //read the next character 411 | while (strncmp_P(&c, START_XML_TAG, 1) != 0) // if we find an XML end tag it means we are at the end of the line 412 | { 413 | _msearchIgdControlUrl += c; 414 | c = _igdClient.read(); //read the next character and append to the string 415 | } 416 | _igdClient.stop(); 417 | free(buffer); 418 | return 1; 419 | } 420 | } 421 | // We have reached the end service wrapper tag - error condition. 422 | // If we reach here this is an error condition because we should have found 423 | // the control URL tags for this service before getting this far. 424 | _igdClient.stop(); 425 | free(buffer); 426 | return 0; 427 | } 428 | } 429 | } 430 | // flush out the full line of characters 431 | c = _igdClient.read(); 432 | while (strncmp(&c, LINE_FEED, 1) != 0) // iterate through the characters until we hit a LF then drop through 433 | { 434 | //Serial.print(c); 435 | c = _igdClient.read(); 436 | } 437 | } //end if (client.available()) 438 | if((millis() - responseStartTime) > IGD_RESPONSE_TIMEOUT) 439 | { 440 | //Serial.println(F("ran out of time!")); 441 | _igdClient.stop(); 442 | free(buffer); 443 | return 0; 444 | } 445 | } //end while (client.connected()) 446 | // if we are here it is because we couldnt find the URL in the XML. return an error condition 447 | _igdClient.stop(); 448 | free(buffer); 449 | return 0; 450 | } //end function parseXmlResponse --------------------------------------------------------------------------------