diff --git a/phpssdp.php b/phpssdp.php
new file mode 100644
index 0000000..7b3dba3
--- /dev/null
+++ b/phpssdp.php
@@ -0,0 +1,238 @@
+
+ *
RESPONSE : Base-64 encoded full device UPNP response
+ * SERVER : Server Name
+ * LOCATION : URI of the XML device description file
+ * ST : Search target value
+ * USN : USN response value (usually a combination of UUID and ST)
+ * IP : IP of the device
+ * UUID : Extracted UUID value of the device
+ * DESCRIPTION : Only added if not calling getAllDevices for performance issue. It contains an array with the content of the node of the XML description file
+ *
+ *
+ * You can use this class to automatically send the array back to the client as a JSON response (in an ajax call context for instance).
+ *
+ * You should not modify default timeout and MX values for best performance. However, if your devices are not responding in time, you can try to increase timeout and/or request shorter MX response delay than timeout. You should also dig in LAN performance issues.
+ *
+ * @author Liqueur de Toile :
+ * @copyright 2017 Liqueur de Toile : https://liqueurdetoile.com
+ *
+ * @licence MIT : https://opensource.org/licenses/MIT
+ */
+ class phpSSDP {
+ /**
+ * Utility method to fetch a device description XML file content.
+ *
+ * Most of the time, troubles will come from timeout issue with slow devices or malformed xml device description file.
+ *
+ * @param string $location URI of the XML file
+ * @return array|null Array with the XML file content of the device node or null if no XML file found
+ */
+ static private function _getDeviceInfo($location) {
+ $curl = curl_init();
+ curl_setopt($curl, CURLOPT_TIMEOUT, 200);
+ curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($curl, CURLOPT_HEADER, true);
+ curl_setopt($curl, CURLOPT_URL, $location);
+ $response = curl_exec($curl);
+ if ( curl_getinfo($curl, CURLINFO_HTTP_CODE) == 200) {
+ $header_size = curl_getinfo($curl, CURLINFO_HEADER_SIZE);
+ $ret = new SimpleXMLElement(substr($response, $header_size));
+ }
+ curl_close($curl);
+ return (!empty($ret->device))?self::_object_to_array($ret->device):null;
+ }
+
+ /**
+ * Utility method to recursively convert an object into an array.
+ * Thanks to Ben Lobaugh for the snippet.
+ * @see https://ben.lobaugh.net/blog/567/php-recursively-convert-an-object-to-an-array Blog of Ben Lobaugh
+ *
+ * @param object $obj Object to be converted
+ * @return array Associative array based on original object
+ */
+ private static function _object_to_array($obj) {
+ if(is_object($obj)) $obj = (array) $obj;
+ if(is_array($obj)) {
+ $new = array();
+ foreach($obj as $key => $val) {
+ $new[$key] = self::_object_to_array($val);
+ }
+ }
+ else $new = $obj;
+ return $new;
+ }
+
+ /**
+ * Utility method to handle result return
+ *
+ * @param array $devices An array of devices
+ * @param boolean $json Triggers a JSON output
+ *
+ * @return array|null Array of devices or null if input devices array is null
+ */
+ private static function _sendResponse($devices, $json) {
+ if($json && !empty($devices)) {
+ http_response_code(200);
+ header('Content-Type: application/json');
+ echo json_encode($devices);
+ }
+ elseif($json && empty($devices)) {
+ http_response_code(204);
+ return null;
+ }
+ return $devices;
+ }
+
+ /**
+ * Utility method to sort a list of devices by IP value
+ *
+ * @param array $devices Array to be sorted
+ * @return array|null Sorted array by IP value or null if input array is empty
+ */
+
+ private static function _sortByIP($devices) {
+ if(empty($devices)) return null;
+ usort($devices, function($k1, $k2) {
+ preg_match("/\d{1,3}$/",$k1['IP'], $ip1);
+ preg_match("/\d{1,3}$/",$k2['IP'], $ip2);
+ return ($ip1[0] - $ip2[0]);
+ });
+ return $devices;
+ }
+
+ /**
+ * Main utility method to perform a multicast SSDP request and build a responding devices array
+ *
+ * By default the MX value is the same than the timeout value, but it can be forced through each callable static methods.
+ *
+ * @param string $st Search value for ST (search target) of the request
+ * @param int $timeout Request timeout value in seconds
+ * @param int $mx MX value in seconds (Response delay for devices)
+ *
+ * @return array|null Array of devices or null if no devices are detected
+ */
+ private static function _search($st, $timeout, $mx = null) {
+ if(is_null($mx)) $mx = $timeout;
+ $headers = "M-SEARCH * HTTP/1.1\r\nHost:239.255.255.250:1900\r\nST:$st\r\nMan:\"ssdp:discover\"\r\nMX:$mx\r\n\r\n";
+ $response = null;
+ $_tmp = null;
+ $devices = array();
+ $keys = ['SERVER','LOCATION','ST','USN'];
+
+ $socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
+ socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, array('sec'=>$timeout, 'usec'=>0));
+ $send_ret = socket_sendto($socket, $headers, 1024, 0, '239.255.255.250', 1900);
+ while(@socket_recvfrom($socket, $response, 1024, MSG_WAITALL, $_tmp, $_tmp)) {
+ $ret=[];
+ $ret['RESPONSE'] = base64_encode($response);
+ //Response analysis
+ foreach($keys as $key) {
+ preg_match("/$key\s*:\s*(.*)/i", $response, $tmp);
+ $ret[$key] = (!empty($tmp[1]))?trim($tmp[1]):'';
+ }
+ preg_match("/http:\/\/(\d+\.\d+\.\d+\.\d+)/i", $response, $tmp);
+ $ret['IP'] = (!empty($tmp[1]))?trim($tmp[1]):'';
+ preg_match("/uuid\s*:\s*([\w-]*)/i", $response, $tmp);
+ $ret['UUID'] = (!empty($tmp[1]))?trim($tmp[1]):'';
+ $devices[] = $ret;
+ }
+ socket_close($socket);
+ return (!empty($devices))?$devices:null;
+ }
+
+ /**
+ * Callable static method to fetch all responding UPNP devices on LAN.
+ *
+ * This method doesn't filter anything and you'll usually have a long list of found items, even duplicates, for some devices
+ *
+ * @param boolean $json Triggers a JSON output
+ * @param int $timeout Defines the timeout value in seconds
+ * @param int $mx MX value in seconds (Response delay for devices)
+ *
+ * @return array An array with the UPNP responses sorted by IP
+ */
+ public static function getAllDevices($json = false, $timeout = 2, $mx = null) {
+ $devices = self::_search('ssdp:all', $timeout, $mx);
+ return self::_sendResponse(self::_sortByIP($devices), $json);
+ }
+
+ /**
+ * Callable static method to fetch all devices which are responding as root devices.
+ *
+ * It's especially useful to have a clean list of all main devices without redundant responses and services. It can cut the results count by ten.
+ *
+ * @param boolean $json Triggers a JSON output
+ * @param int $timeout Defines the timeout value in seconds
+ * @param int $mx MX value in seconds (Response delay for devices)
+ *
+ * @return array An array sorted by IP with the devices list and full description for each device
+ */
+ public static function getAllRootDevices($json = false, $timeout = 2, $mx = null) {
+ // Performs SSDP upnp:rootdevice
+ $devices = self::_search('upnp:rootdevice', $timeout, $mx);
+ //Cleaning list
+ $list = [];
+ foreach($devices as $device) {
+ if( $device['ST'] == 'upnp:rootdevice' && empty($list[$device['UUID']]) ) {
+ $list[$device['UUID']] = $device;
+ //Fetching additionnal informations
+ $list[$device['UUID']]['DESCRIPTION'] = self::_getDeviceInfo($device['LOCATION']);
+ }
+ }
+ return self::_sendResponse(self::_sortByIP($list), $json);
+ }
+
+ /**
+ * Callable static method to launch a custom LAN search for a specific device or service given an URN description
+ *
+ * @param string $urn Device or service to find
+ * @param boolean $json Triggers a JSON output
+ * @param int $timeout Defines the timeout value in seconds
+ * @param int $mx MX value in seconds (Response delay for devices)
+ *
+ * @return array An array with the devices description
+ */
+ public static function getDevicesByURN($urn, $json = false, $timeout = 1, $mx = null) {
+ $devices = self::_search($urn, $timeout, $mx);
+ //Fetching additional informations
+ foreach($devices as $key => $device) $devices[$key]['DESCRIPTION'] = self::_getDeviceInfo($device['LOCATION']);
+ return self::_sendResponse(self::_sortByIP($devices), $json);
+ }
+
+ /**
+ * Callable static method to look for a device with a given UUID.
+ *
+ * Following standards, an UUID value SHOULD be unique, like a MAC address. However, it's still possible to encounter an UUID conflict.
+ * In this case, this method will return only the first result.
+ *
+ * @link https://en.wikipedia.org/wiki/Universally_unique_identifier
+ *
+ * @param string $uuid Device UUID to query
+ * @param boolean $json Triggers a JSON output
+ * @param int $timeout Defines the timeout value in seconds
+ * @param int $mx MX value in seconds (Response delay for devices)
+ *
+ * @return array An array with the device description
+ */
+ public static function getDeviceByUUID($uuid, $json = false, $timeout = 1, $mx = null) {
+ $device = null;
+ $devices = self::_search('uuid:'.$uuid, $timeout, $mx);
+ if(!empty($devices)) $device = $devices[0];
+ //Fetching additionnal informations
+ $device['DESCRIPTION'] = self::_getDeviceInfo($device['LOCATION']);
+ return self::_sendResponse($device, $json);
+ }
+}
\ No newline at end of file