diff --git a/README.md b/README.md index e1d7e19..223d57d 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,14 @@ object to get the devices. print_r($device); } +## Experimental Features + +### DCP + +The DCPs (Device Control Protocol) are used to interact with services. This +can be f.ex. managing UPnP portmapping in your router or controlling a media +player. + ## Search targets To find everything, use `ALL`: diff --git a/bin/upnp-discover b/bin/upnp-discover index 94ec321..14cb075 100755 --- a/bin/upnp-discover +++ b/bin/upnp-discover @@ -10,10 +10,11 @@ require_once __DIR__."/../vendor/autoload.php"; +use NoccyLabs\UPnP\SSDP\Device; use NoccyLabs\UPnP\SSDP\Discovery; use NoccyLabs\UPnP\SSDP\SearchTarget; -$opt_short = "hARd:s:u:D:l"; +$opt_short = "hARd:s:u:D:lj"; $opt_long = [ "help", // -h "all", // -A @@ -22,7 +23,8 @@ $opt_long = [ "service:", // -s: "uuid:", // -u: "domain:", // -D: - "long", + "long", // -l + "json", // -j ]; $help = <<null, 'domain'=>null, 'uuid'=>null, - 'long'=>null + 'long'=>null, + 'json'=>null ]; foreach (getopt($opt_short, $opt_long) as $opt=>$value) switch ($opt) { @@ -97,6 +101,9 @@ foreach (getopt($opt_short, $opt_long) as $opt=>$value) switch ($opt) { case 'long': case 'l': $opts->long = true; break; + case 'json': + case 'j': + $opts->json = true; break; } if ($opts->device && $opts->service) { @@ -190,17 +197,54 @@ function discover_root() { } function show_results(Discovery $discovery) { - foreach ($discovery as $device) { - printf(" %s: %s (%s) %s [%s]\n", - $device->getFriendlyName(), - $device->getModelName(), - $device->getManufacturer(), - $device->getDeviceType(), - $device->getIp() - ); - foreach ($device->getServices() as $service) { - printf(" + %s\n", $service->getServiceType()); - } + global $opts; + if ($opts->long) { + show_results_long($discovery); + } elseif ($opts->json) { + show_results_json($discovery); + } else { + show_results_short($discovery); } - -} \ No newline at end of file +} + +function show_results_short(Discovery $discovery) { + foreach ($discovery as $device) { + printf(" * \e[94m%s: %s \e[1m%s\e[21m (%s) [%s]\e[0m\n", + $device->getFriendlyName(), + $device->getManufacturer(), + $device->getModelName(), + $device->getDeviceType(), + $device->getIp() + ); + } +} + + + +function show_results_long(Discovery $discovery) { + foreach ($discovery as $device) { + show_device_long($device); + } +} + +function show_device_long(Device $device, $level=0) { + $indent = str_repeat(" ",$level); + printf("{$indent} * \e[92m%s\e[94m: %s \e[1m%s\e[21m (\e[34m%s\e[94m) [%s]\e[0m\n", + $device->getFriendlyName(), + $device->getManufacturer(), + $device->getModelName(), + $device->getDeviceType(), + $device->getIp() + ); + printf("{$indent} \e[32m%s\e[0m\n", $device->getUrl()); + foreach ($device->getServices() as $service) { + printf("{$indent} + \e[36m%s\e[0m\n{$indent} \e[32m%s\e[0m\n", $service->getServiceType(), $service->getServiceUrl()); + } + foreach ($device->getDevices() as $subdevice) { + show_device_long($subdevice, $level+1); + } +} + +function show_results_json(Discovery $discovery) { + echo json_encode($discovery, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES)."\n"; +} diff --git a/src/DCP/AbstractDevice.php b/src/DCP/AbstractDevice.php new file mode 100644 index 0000000..43629f1 --- /dev/null +++ b/src/DCP/AbstractDevice.php @@ -0,0 +1,22 @@ +device = $device; + } +} \ No newline at end of file diff --git a/src/DCP/AbstractService.php b/src/DCP/AbstractService.php new file mode 100644 index 0000000..8d3c3e0 --- /dev/null +++ b/src/DCP/AbstractService.php @@ -0,0 +1,38 @@ +device = $device; + $this->service = $service; + } + + private function readScpdServiceDefinition($url) + {} + + /** + * Get the schema to pass as the XML namespace when sending the SOAP request + * to the device. + * + * @return string The schema + */ + abstract protected function getSchemaUrn(); +} \ No newline at end of file diff --git a/src/SSDP/Device.php b/src/SSDP/Device.php index a0570a6..db22b8a 100644 --- a/src/SSDP/Device.php +++ b/src/SSDP/Device.php @@ -17,8 +17,9 @@ namespace NoccyLabs\UPnP\SSDP; */ use SimpleXMLElement; +use JsonSerializable; -class Device +class Device implements JsonSerializable { protected $deviceType; @@ -86,7 +87,7 @@ class Device $services = $spec->serviceList; if (count($services)>0) { foreach ($services->children() as $service) { - $this->services[] = new Service($service); + $this->services[] = new Service($this, $service); } } @@ -163,5 +164,16 @@ class Device return $info; } + + + public function createServiceWrapper($type) + { + + } + + public function jsonSerialize() + { + return get_object_vars($this); + } } diff --git a/src/SSDP/Discovery.php b/src/SSDP/Discovery.php index 670a2bb..9061095 100644 --- a/src/SSDP/Discovery.php +++ b/src/SSDP/Discovery.php @@ -4,6 +4,7 @@ namespace NoccyLabs\UPnP\SSDP; use IteratorAggregate; use ArrayIterator; +use JsonSerializable; use NoccyLabs\UPnP\HTTPU\Endpoint; use NoccyLabs\UPnP\HTTPU\EndpointException; use NoccyLabs\UPnP\HTTPU\MSearchRequest; @@ -14,7 +15,7 @@ use NoccyLabs\UPnP\HTTPU\MSearchResponse; * * */ -class Discovery implements IteratorAggregate +class Discovery implements IteratorAggregate, JsonSerializable { protected $devices = []; @@ -52,10 +53,14 @@ class Discovery implements IteratorAggregate continue; } $device = Device::createFromSchema($response->getLocation(), $response->getIp()); - $this->devices[$loc] = $device; + if ($device) { + $this->devices[$loc] = $device; + } } } + $this->devices = array_values($this->devices); + return count($this->devices); } @@ -65,5 +70,10 @@ class Discovery implements IteratorAggregate return new ArrayIterator($this->devices); } + public function jsonSerialize() + { + return $this->devices; + } + } diff --git a/src/SSDP/Service.php b/src/SSDP/Service.php index b90fa9b..5dc5297 100644 --- a/src/SSDP/Service.php +++ b/src/SSDP/Service.php @@ -10,10 +10,13 @@ namespace NoccyLabs\UPnP\SSDP; /evt/L3F */ +use JsonSerializable; use SimpleXMLElement; -class Service +class Service implements JsonSerializable { + protected $device; + protected $serviceType; protected $serviceId; @@ -24,9 +27,10 @@ class Service protected $eventSubUrl; - public function __construct(SimpleXMLElement $spec) + public function __construct(Device $device, SimpleXMLElement $spec) { + $this->device = $device; $this->serviceType = (string)$spec->serviceType; $this->serviceId = (string)$spec->serviceId; $this->scpdUrl = (string)$spec->SCPDURL; @@ -50,6 +54,26 @@ class Service return $this->scpdUrl; } + public function getServiceUrl() + { + if (strpos($this->scpdUrl,"://")!==false) { + return $this->scpdUrl; + } + + $dev = ['user'=>null, 'pass'=>null, 'port'=>80, 'path'=>null]; + $dev = array_merge($dev, parse_url($this->device->getUrl())); + $url = $this->scpdUrl; + + $auth = $dev['user']?($dev['user'].":".$dev['path']):""; + $base = $dev['scheme']."://".$dev['host'].":".($dev['port']?:80); + if ($url[0]=="/") { + return $base.$url; + } else { + return $base.rtrim($dev['path'],"/")."/".$url; + } + + } + public function getControlUrl() { return $this->controlUrl; @@ -71,5 +95,10 @@ class Service ); } + public function jsonSerialize() + { + return get_object_vars($this); + } + }