ControlPoint.pm 42 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663
  1. ########################################################################################
  2. #
  3. # ControlPoint.pm
  4. #
  5. # $Id: ControlPoint.pm 14715 2017-07-14 10:39:57Z Reinerlein $
  6. #
  7. # Now (in this version) part of Fhem.
  8. #
  9. # Fhem is free software: you can redistribute it and/or modify
  10. # it under the terms of the GNU General Public License as published by
  11. # the Free Software Foundation, either version 2 of the License, or
  12. # (at your option) any later version.
  13. #
  14. # Fhem is distributed in the hope that it will be useful,
  15. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. # GNU General Public License for more details.
  18. #
  19. # You should have received a copy of the GNU General Public License
  20. # along with fhem. If not, see <http://www.gnu.org/licenses/>.
  21. #
  22. ########################################################################################
  23. package UPnP::ControlPoint;
  24. use 5.006;
  25. use strict;
  26. use warnings;
  27. use utf8;
  28. use Carp;
  29. use IO::Socket::INET;
  30. use Socket;
  31. use IO::Select;
  32. use HTTP::Daemon;
  33. use HTTP::Headers;
  34. use LWP::UserAgent;
  35. use UPnP::Common;
  36. use vars qw($VERSION @ISA);
  37. require Exporter;
  38. our @ISA = qw(Exporter UPnP::Common::DeviceLoader);
  39. our $VERSION = $UPnP::Common::VERSION;
  40. use constant DEFAULT_SSDP_SEARCH_PORT => 8008;
  41. use constant DEFAULT_SUBSCRIPTION_PORT => 8058;
  42. use constant DEFAULT_SUBSCRIPTION_URL => '/eventSub';
  43. our %IGNOREIP;
  44. our %USEDONLYIP;
  45. sub new {
  46. my($self, %args) = @_;
  47. my $class = ref($self) || $self;
  48. $self = $class->SUPER::new(%args);
  49. my $searchPort = defined($args{SearchPort}) ? $args{SearchPort} : DEFAULT_SSDP_SEARCH_PORT;
  50. my $subscriptionPort = defined($args{SubscriptionPort}) ? $args{SubscriptionPort} : DEFAULT_SUBSCRIPTION_PORT;
  51. my $maxWait = $args{MaxWait} || 3;
  52. %IGNOREIP = %{$args{IgnoreIP}};
  53. %USEDONLYIP = %{$args{UsedOnlyIP}};
  54. my $reuseport = $args{ReusePort};
  55. $reuseport = 0 if (!defined($reuseport));
  56. # Create the socket on which search requests go out
  57. $self->{_searchSocket} = IO::Socket::INET->new(Proto => 'udp', LocalPort => $searchPort) || carp("Error creating search socket: $!\n");
  58. setsockopt($self->{_searchSocket},
  59. IP_LEVEL,
  60. IP_MULTICAST_TTL,
  61. pack 'I', 4);
  62. $self->{_maxWait} = $maxWait;
  63. # Create the socket on which we'll listen for events to which we are
  64. # subscribed.
  65. $self->{_subscriptionSocket} = HTTP::Daemon->new(LocalPort => $subscriptionPort, Reuse=>1, Listen=>20) || carp("Error creating subscription socket: $!\n");
  66. $self->{_subscriptionURL} = $args{SubscriptionURL} || DEFAULT_SUBSCRIPTION_URL;
  67. $self->{_subscriptionPort} = $self->{_subscriptionSocket}->sockport();;
  68. # Create the socket on which we'll listen for SSDP Notifications.
  69. # First try with ReusePort (if given as parameter)...
  70. eval {
  71. $self->{_ssdpMulticastSocket} = IO::Socket::INET->new(
  72. Proto => 'udp',
  73. Reuse => 1,
  74. ReusePort => $reuseport,
  75. LocalPort => SSDP_PORT) ||
  76. croak("Error creating SSDP multicast listen socket: $!\n");
  77. };
  78. if ($@ =~ /Your vendor has not defined Socket macro SO_REUSEPORT/i) {
  79. $self->{_ssdpMulticastSocket} = IO::Socket::INET->new(
  80. Proto => 'udp',
  81. Reuse => 1,
  82. LocalPort => SSDP_PORT) ||
  83. croak("Error creating SSDP multicast listen socket: $!\n");
  84. } elsif($@) {
  85. # Weiterwerfen...
  86. croak($@);
  87. }
  88. my $ip_mreq = inet_aton(SSDP_IP) . INADDR_ANY;
  89. setsockopt($self->{_ssdpMulticastSocket},
  90. IP_LEVEL,
  91. IP_ADD_MEMBERSHIP,
  92. $ip_mreq);
  93. setsockopt($self->{_ssdpMulticastSocket},
  94. IP_LEVEL,
  95. IP_MULTICAST_TTL,
  96. pack 'I', 4);
  97. return $self;
  98. }
  99. sub DESTROY {
  100. my $self = shift;
  101. for my $subscription (values %{$self->{_subscriptions}}) {
  102. if ($subscription) {
  103. $subscription->unsubscribe;
  104. }
  105. }
  106. }
  107. sub searchByType {
  108. my $self = shift;
  109. my $type = shift;
  110. my $callback = shift;
  111. my $search = UPnP::ControlPoint::Search->new(Callback => $callback,
  112. Type => $type);
  113. $self->{_activeSearches}->{$search} = $search;
  114. $self->_startSearch($type);
  115. return $search;
  116. }
  117. sub searchByUDN {
  118. my $self = shift;
  119. my $udn = shift;
  120. my $callback = shift;
  121. my $search = UPnP::ControlPoint::Search->new(Callback => $callback,
  122. UDN => $udn);
  123. $self->{_activeSearches}->{$search} = $search;
  124. $self->_startSearch("upnp:rootdevice");
  125. $search;
  126. }
  127. sub searchByFriendlyName {
  128. my $self = shift;
  129. my $name = shift;
  130. my $callback = shift;
  131. my $search = UPnP::ControlPoint::Search->new(Callback => $callback,
  132. FriendlyName => $name);
  133. $self->{_activeSearches}->{$search} = $search;
  134. $self->_startSearch("upnp:rootdevice");
  135. $search;
  136. }
  137. sub stopSearch {
  138. my $self = shift;
  139. my $search = shift;
  140. delete $self->{_activeSearches}->{$search};
  141. }
  142. sub sockets {
  143. my $self = shift;
  144. return ($self->{_subscriptionSocket},
  145. $self->{_ssdpMulticastSocket},
  146. $self->{_searchSocket},);
  147. }
  148. sub handleOnce {
  149. my $self = shift;
  150. my $socket = shift;
  151. if ($socket == $self->{_searchSocket}) {
  152. $self->_receiveSearchResponse($socket);
  153. }
  154. elsif ($socket == $self->{_ssdpMulticastSocket}) {
  155. $self->_receiveSSDPEvent($socket);
  156. }
  157. elsif ($socket == $self->{_subscriptionSocket}) {
  158. if (my $connect = $socket->accept()) {
  159. return if (scalar(%USEDONLYIP) && (!$USEDONLYIP{$connect->peerhost()}));
  160. return if ($IGNOREIP{$connect->peerhost()});
  161. $self->_receiveSubscriptionNotification($connect);
  162. }
  163. }
  164. }
  165. sub handle {
  166. my $self = shift;
  167. my @mysockets = $self->sockets();
  168. my $select = IO::Select->new(@mysockets);
  169. $self->{_handling} = 1;
  170. while ($self->{_handling}) {
  171. my @sockets = $select->can_read(1);
  172. for my $sock (@sockets) {
  173. $self->handleOnce($sock);
  174. }
  175. }
  176. }
  177. sub stopHandling {
  178. my $self = shift;
  179. $self->{_handling} = 0;
  180. }
  181. sub subscriptionURL {
  182. my $self = shift;
  183. return URI->new_abs($self->{_subscriptionURL},
  184. 'http://' . UPnP::Common::getLocalIP() . ':' .
  185. $self->{_subscriptionPort});
  186. }
  187. sub addSubscription {
  188. my $self = shift;
  189. my $subscription = shift;
  190. $self->{_subscriptions}->{$subscription->SID} = $subscription;
  191. }
  192. sub removeSubscription {
  193. my $self = shift;
  194. my $subscription = shift;
  195. delete $self->{_subscriptions}->{$subscription->SID};
  196. }
  197. sub _startSearch {
  198. my $self = shift;
  199. my $target = shift;
  200. my $header = 'M-SEARCH * HTTP/1.1' . CRLF .
  201. 'HOST: ' . SSDP_IP . ':' . SSDP_PORT . CRLF .
  202. 'MAN: "ssdp:discover"' . CRLF .
  203. 'ST: ' . $target . CRLF .
  204. 'MX: ' . $self->{_maxWait} . CRLF .
  205. CRLF;
  206. my $destaddr = sockaddr_in(SSDP_PORT, inet_aton(SSDP_IP));
  207. send($self->{_searchSocket}, $header, 0, $destaddr);
  208. }
  209. sub _parseUSNHeader {
  210. my $usn = shift;
  211. my ($udn, $deviceType, $serviceType);
  212. if ($usn =~ /^uuid:schemas(.*?):device(.*?):(.*?):(.+)$/) {
  213. $udn = 'uuid:' . $4;
  214. $deviceType = 'urn:schemas' . $1 . ':device' . $2 . ':' . $3;
  215. }
  216. elsif ($usn =~ /^uuid:(.+?)::/) {
  217. $udn = 'uuid:' . $1;
  218. if ($usn =~ /urn:(.+)$/) {
  219. my $urn = $1;
  220. if ($usn =~ /:service:/) {
  221. $serviceType = 'urn:' . $urn;
  222. }
  223. elsif ($usn =~ /:device:/) {
  224. $deviceType = 'urn:' . $urn;
  225. }
  226. }
  227. }
  228. else {
  229. $udn = $usn;
  230. }
  231. return ($udn, $deviceType, $serviceType);
  232. }
  233. sub _firstLocation {
  234. my $headers = shift;
  235. my $location = $headers->header('Location');
  236. return $location if $location;
  237. my $al = $headers->header('AL');
  238. if ($al && $al =~ /^<(\S+?)>/) {
  239. return $1;
  240. }
  241. return undef;
  242. }
  243. sub newService {
  244. my $self = shift;
  245. return UPnP::ControlPoint::Service->new(@_);
  246. }
  247. sub newDevice {
  248. my $self = shift;
  249. return UPnP::ControlPoint::Device->new(@_);
  250. }
  251. sub _createDevice {
  252. my $self = shift;
  253. my $location = shift;
  254. my $device;
  255. # We've found examples of where devices claim to do transfer
  256. # encoding, but wind up sending chunks without chunk size headers.
  257. # This code temporarily disables the TE header in the request.
  258. #push(@LWP::Protocol::http::EXTRA_SOCK_OPTS, SendTE => 0);
  259. my @SOCK_OPTS_Backup = @LWP::Protocol::http::EXTRA_SOCK_OPTS;
  260. _addSendTE();
  261. my $ua = LWP::UserAgent->new(timeout => 20);
  262. my $response = $ua->get($location);
  263. my $base;
  264. if ($response->is_success && $response->content ne '') {
  265. ($device, $base) = $self->parseDeviceDescription($response->content,
  266. {Location => $location},
  267. {ControlPoint => $self});
  268. } else {
  269. carp('400-URL-Absolute-Error! Location: "'.$location.'", Content: "'.$response->content.'"') if ($response->code == 400);
  270. carp("Loading device description failed with error: " . $response->code . " " . $response->message . ' (Location: ' . $location . ')') if ($response->code != 200);
  271. }
  272. #pop(@LWP::Protocol::http::EXTRA_SOCK_OPTS);
  273. @LWP::Protocol::http::EXTRA_SOCK_OPTS = @SOCK_OPTS_Backup;
  274. if ($device) {
  275. $device->base($base ? $base : $location);
  276. if ($response->is_success && $response->content ne '') {
  277. $device->descriptionDocument($response->content);
  278. }
  279. }
  280. return $device;
  281. }
  282. sub _addSendTE {
  283. my %arg = @LWP::Protocol::http::EXTRA_SOCK_OPTS;
  284. $arg{SendTE} = 0;
  285. @LWP::Protocol::http::EXTRA_SOCK_OPTS = %arg;
  286. }
  287. sub _getDeviceFromHeaders {
  288. my $self = shift;
  289. my $headers = shift;
  290. my $create = shift;
  291. my $location = _firstLocation($headers);
  292. my ($udn, $deviceType, $serviceType) =
  293. _parseUSNHeader($headers->header('USN'));
  294. my $device = $self->{_devices}->{$udn};
  295. if (!defined($device) && $create) {
  296. $device = $self->_createDevice($location);
  297. if ($device) {
  298. $self->{_devices}->{$udn} = $device;
  299. }
  300. }
  301. return $device;
  302. }
  303. sub _deviceAdded {
  304. my $self = shift;
  305. my $device = shift;
  306. for my $search (values %{$self->{_activeSearches}}) {
  307. $search->deviceAdded($device);
  308. }
  309. }
  310. sub _deviceRemoved {
  311. my $self = shift;
  312. my $device = shift;
  313. for my $search (values %{$self->{_activeSearches}}) {
  314. $search->deviceRemoved($device);
  315. }
  316. }
  317. use Data::Dumper;
  318. sub _receiveSearchResponse {
  319. my $self = shift;
  320. my $socket = shift;
  321. my $buf = '';
  322. my $peer = recv($socket, $buf, 2048, 0);
  323. my @peerdata = unpack_sockaddr_in($peer);
  324. return if (scalar(%USEDONLYIP) && (!$USEDONLYIP{inet_ntoa($peerdata[1])}));
  325. return if ($IGNOREIP{inet_ntoa($peerdata[1])});
  326. if ($buf !~ /\015?\012\015?\012/) {
  327. return;
  328. }
  329. $buf =~ s/^(?:\015?\012)+//; # ignore leading blank lines
  330. unless ($buf =~ s/^(\S+)[ \t]+(\S+)[ \t]+(\S+)[^\012]*\012//) {
  331. # Bad header
  332. return;
  333. }
  334. # Basic check to see if the response is actually for a search
  335. my $found = 0;
  336. foreach my $searchkey (keys %{$self->{_activeSearches}}) {
  337. my $search = $self->{_activeSearches}->{$searchkey};
  338. if ($search->{_type} && $buf =~ $search->{_type}) {
  339. $found = 1;
  340. last;
  341. }
  342. if ($search->{_udn} && $buf =~ $search->{_udn}) {
  343. $found = 1;
  344. last;
  345. }
  346. if ($search->{_friendlyName} && $buf =~ $search->{_friendlyName}) {
  347. $found = 1;
  348. last;
  349. }
  350. }
  351. if (! $found) {
  352. #print "Unknown response: " . Dumper($buf); #ALW uncomment
  353. return;
  354. }
  355. my $code = $2;
  356. if ($code ne '200') {
  357. # We expect a success response code
  358. return;
  359. }
  360. my $headers = UPnP::Common::parseHTTPHeaders($buf);
  361. my $device = $self->_getDeviceFromHeaders($headers, 1);
  362. if ($device) {
  363. $self->_deviceAdded($device);
  364. }
  365. }
  366. sub _receiveSSDPEvent {
  367. my $self = shift;
  368. my $socket = shift;
  369. my $buf = '';
  370. my $peer = recv($socket, $buf, 2048, 0);
  371. return if (!defined($peer));
  372. my @peerdata = unpack_sockaddr_in($peer);
  373. return if (!@peerdata);
  374. return if (scalar(%USEDONLYIP) && (!$USEDONLYIP{inet_ntoa($peerdata[1])}));
  375. return if ($IGNOREIP{inet_ntoa($peerdata[1])});
  376. if ($buf !~ /\015?\012\015?\012/) {
  377. return;
  378. }
  379. $buf =~ s/^(?:\015?\012)+//; # ignore leading blank lines
  380. unless ($buf =~ s/^(\S+)[ \t]+(\S+)(?:[ \t]+(HTTP\/\d+\.\d+))?[^\012]*\012//) {
  381. # Bad header
  382. return;
  383. }
  384. #print Dumper($buf); #ALW uncomment
  385. my $method = $1;
  386. if ($method ne 'NOTIFY') {
  387. # We only care about notifications
  388. return;
  389. }
  390. my $headers = UPnP::Common::parseHTTPHeaders($buf);
  391. my $eventType = $headers->header('NTS');
  392. my $device = $self->_getDeviceFromHeaders($headers,
  393. $eventType =~ /alive/ ?
  394. 1 : 0);
  395. if ($device) {
  396. if ($eventType =~ /alive/) {
  397. $self->_deviceAdded($device);
  398. }
  399. elsif ($eventType =~ /byebye/) {
  400. $self->_deviceRemoved($device);
  401. $self->{_devices}->{$device->UDN()} = undef;
  402. }
  403. }
  404. }
  405. sub _parseProperty {
  406. my $self = shift;
  407. my $element = shift;
  408. my ($name, $attrs, $children) = @$element;
  409. my ($key, $value);
  410. if ($name =~ /property/) {
  411. my $childElement = $children->[0];
  412. $key = $childElement->[0];
  413. $value = $childElement->[2];
  414. }
  415. ($key, $value);
  416. }
  417. sub _parsePropertySet {
  418. my $self = shift;
  419. my $content = shift;
  420. my %properties = ();
  421. my $parser = $self->parser;
  422. my $element = $parser->parse($content);
  423. if (defined($element) && (ref $element eq 'ARRAY') &&
  424. $element->[0] =~ /propertyset/) {
  425. my($name, $attrs, $children) = @$element;
  426. for my $child (@$children) {
  427. my ($key, $value) = $self->_parseProperty($child);
  428. if ($key) {
  429. $properties{$key} = $value;
  430. }
  431. }
  432. }
  433. return %properties;
  434. }
  435. sub _receiveSubscriptionNotification {
  436. my $self = shift;
  437. my $connect = shift;
  438. my $request = $connect->get_request();
  439. if ($request && ($request->method eq 'NOTIFY') &&
  440. ($request->header('NT') eq 'upnp:event') &&
  441. ($request->header('NTS') eq 'upnp:propchange')) {
  442. my $sid = $request->header('SID');
  443. my $subscription = $self->{_subscriptions}->{$sid};
  444. if ($subscription) {
  445. my %propSet = $self->_parsePropertySet($request->content);
  446. $subscription->propChange(%propSet);
  447. }
  448. }
  449. $connect->send_response(HTTP::Response->new(HTTP::Status::RC_OK));
  450. $connect->close;
  451. }
  452. # ----------------------------------------------------------------------
  453. package UPnP::ControlPoint::Device;
  454. use strict;
  455. use vars qw(@ISA);
  456. use UPnP::Common;
  457. our @ISA = qw(UPnP::Common::Device);
  458. sub base {
  459. my $self = shift;
  460. my $base = shift;
  461. if (defined($base)) {
  462. $self->{_base} = $base;
  463. for my $service ($self->services) {
  464. $service->base($base);
  465. }
  466. for my $device ($self->children) {
  467. $device->base($base);
  468. }
  469. }
  470. return $self->{_base};
  471. }
  472. sub descriptionDocument {
  473. my $self = shift;
  474. my $descriptionDocument = shift;
  475. if (defined($descriptionDocument)) {
  476. $self->{_descriptionDocument} = $descriptionDocument;
  477. }
  478. return $self->{_descriptionDocument};
  479. }
  480. # ----------------------------------------------------------------------
  481. package UPnP::ControlPoint::Service;
  482. use strict;
  483. use Socket;
  484. use Scalar::Util qw(weaken);
  485. use SOAP::Lite;
  486. use Carp;
  487. use vars qw($AUTOLOAD @ISA %urlProperties);
  488. use UPnP::Common;
  489. our @ISA = qw(UPnP::Common::Service);
  490. for my $prop (qw(SCPDURL controlURL eventSubURL)) {
  491. $urlProperties{$prop}++;
  492. }
  493. sub new {
  494. my ($self, %args) = @_;
  495. my $class = ref($self) || $self;
  496. $self = $class->SUPER::new(%args);
  497. if ($args{ControlPoint}) {
  498. $self->{_controlPoint} = $args{ControlPoint};
  499. weaken($self->{_controlPoint});
  500. }
  501. return $self;
  502. }
  503. sub AUTOLOAD {
  504. my $self = shift;
  505. my $attr = $AUTOLOAD;
  506. $attr =~ s/.*:://;
  507. return if $attr eq 'DESTROY';
  508. my $superior = "SUPER::$attr";
  509. my $val = $self->$superior(@_);
  510. if ($urlProperties{$attr}) {
  511. my $base = $self->base;
  512. if ($base) {
  513. return URI->new_abs($val, $base);
  514. }
  515. return URI->new($val);
  516. }
  517. return $val;
  518. }
  519. sub controlProxy {
  520. my $self = shift;
  521. $self->_loadDescription;
  522. return UPnP::ControlPoint::ControlProxy->new($self);
  523. }
  524. sub queryStateVariable {
  525. my $self = shift;
  526. my $name = shift;
  527. $self->_loadDescription;
  528. my $var = $self->getStateVariable($name);
  529. if (!$var) { croak("No such state variable $name"); }
  530. if (!$var->evented) { croak("Variable $name is not evented"); }
  531. my $result;
  532. if ($SOAP::Lite::VERSION >= 0.67) {
  533. $result = SOAP::Lite
  534. ->ns("u")
  535. ->uri('urn:schemas-upnp-org:control-1-0')
  536. ->proxy($self->controlURL)
  537. ->call('QueryStateVariable' =>
  538. SOAP::Data->name('varName')
  539. ->uri('urn:schemas-upnp-org:control-1-0')
  540. ->value($name));
  541. } else {
  542. $result = SOAP::Lite
  543. ->uri('urn:schemas-upnp-org:control-1-0')
  544. ->proxy($self->controlURL)
  545. ->call('QueryStateVariable' =>
  546. SOAP::Data->name('varName')
  547. ->uri('urn:schemas-upnp-org:control-1-0')
  548. ->value($name));
  549. }
  550. if ($result->fault()) {
  551. carp("Query failed with fault " . $result->faultstring());
  552. return undef;
  553. }
  554. return $result->result;
  555. }
  556. sub subscribe {
  557. my $self = shift;
  558. my $callback = shift;
  559. my $timeout = shift;
  560. my $cp = $self->{_controlPoint};
  561. if (!defined $UPnP::Common::LocalIP) {
  562. # Find our local IP
  563. my $u = URI->new($self->eventSubURL);
  564. my $proto = getprotobyname('tcp');
  565. socket(Socket_Handle, PF_INET, SOCK_STREAM, $proto);
  566. my $sin = sockaddr_in($u->port(),inet_aton($u->host()));
  567. connect(Socket_Handle,$sin);
  568. my ($port, $addr) = sockaddr_in(getsockname(Socket_Handle));
  569. close(Socket_Handle);
  570. UPnP::Common::setLocalIP($addr);
  571. }
  572. if (defined($cp)) {
  573. my $url = $self->eventSubURL;
  574. my $request = HTTP::Request->new('SUBSCRIBE',
  575. "$url");
  576. $request->header('NT', 'upnp:event');
  577. $request->header('Callback', '<' . $cp->subscriptionURL . '>');
  578. $request->header('Timeout',
  579. 'Second-' . defined($timeout) ? $timeout : 'infinite');
  580. my $ua = LWP::UserAgent->new(timeout => 20);
  581. my $response = $ua->request($request);
  582. if ($response->is_success) {
  583. if ($response->code == 200) {
  584. my $sid = $response->header('SID');
  585. $timeout = $response->header('Timeout');
  586. if ($timeout =~ /^Second-(\d+)$/) {
  587. $timeout = $1;
  588. }
  589. my $subscription = UPnP::ControlPoint::Subscription->new(
  590. Service => $self,
  591. Callback => $callback,
  592. SID => $sid,
  593. Timeout => $timeout,
  594. EventSubURL => "$url");
  595. $cp->addSubscription($subscription);
  596. return $subscription;
  597. } else {
  598. carp("Subscription request successful but answered with error: " . $response->code . " " . $response->message);
  599. }
  600. } else {
  601. carp("Subscription request failed with error: " . $response->code . " " . $response->message);
  602. }
  603. }
  604. return undef;
  605. }
  606. sub unsubscribe {
  607. my $self = shift;
  608. my $subscription = shift;
  609. my $url = $self->eventSubURL;
  610. my $request = HTTP::Request->new('UNSUBSCRIBE',
  611. "$url");
  612. $request->header('SID', $subscription->SID);
  613. my $ua = LWP::UserAgent->new(timeout => 20);
  614. my $response = $ua->request($request);
  615. if ($response->is_success) {
  616. my $cp = $self->{_controlPoint};
  617. if (defined($cp)) {
  618. $cp->removeSubscription($subscription);
  619. }
  620. }
  621. else {
  622. if ($response->code != 412) {
  623. carp("Unsubscription request failed with error: " .
  624. $response->code . " " . $response->message);
  625. }
  626. }
  627. }
  628. sub _loadDescription {
  629. my $self = shift;
  630. if ($self->{_loadedDescription}) {
  631. return;
  632. }
  633. my $location = $self->SCPDURL;
  634. my $cp = $self->{_controlPoint};
  635. unless (defined($location)) {
  636. carp("Service doesn't have a SCPD location");
  637. return;
  638. }
  639. unless (defined($cp)) {
  640. carp("ControlPoint instance no longer exists");
  641. return;
  642. }
  643. my $parser = $cp->parser;
  644. #push(@LWP::Protocol::http::EXTRA_SOCK_OPTS, SendTE => 0);
  645. my @SOCK_OPTS_Backup = @LWP::Protocol::http::EXTRA_SOCK_OPTS;
  646. _addSendTE();
  647. my $ua = LWP::UserAgent->new(timeout => 20);
  648. my $response = $ua->get($location);
  649. if ($response->is_success) {
  650. $self->parseServiceDescription($parser, $response->content);
  651. }
  652. else {
  653. carp("Error loading SCPD document: $!");
  654. }
  655. #pop(@LWP::Protocol::http::EXTRA_SOCK_OPTS);
  656. @LWP::Protocol::http::EXTRA_SOCK_OPTS = @SOCK_OPTS_Backup;
  657. $self->{_loadedDescription} = 1;
  658. }
  659. sub _addSendTE {
  660. my %arg = @LWP::Protocol::http::EXTRA_SOCK_OPTS;
  661. $arg{SendTE} = 0;
  662. @LWP::Protocol::http::EXTRA_SOCK_OPTS = %arg;
  663. }
  664. # ----------------------------------------------------------------------
  665. package UPnP::ControlPoint::ControlProxy;
  666. use strict;
  667. use SOAP::Lite;
  668. use Carp;
  669. use vars qw($AUTOLOAD);
  670. sub new {
  671. my($class, $service) = @_;
  672. if ($SOAP::Lite::VERSION >= 0.67) {
  673. return bless {
  674. _service => $service,
  675. _proxy => SOAP::Lite->ns("u")->uri($service->serviceType)->proxy($service->controlURL),
  676. }, $class;
  677. } else {
  678. return bless {
  679. _service => $service,
  680. _proxy => SOAP::Lite->uri($service->serviceType)->proxy($service->controlURL),
  681. }, $class;
  682. }
  683. }
  684. sub AUTOLOAD {
  685. my $self = shift;
  686. my $service = $self->{_service};
  687. my $proxy = $self->{_proxy};
  688. my $method = $AUTOLOAD;
  689. $method =~ s/.*:://;
  690. return if $method eq 'DESTROY';
  691. my $action = $service->getAction($method);
  692. croak "invalid method: ->$method()" unless $action;
  693. my @inArgs;
  694. for my $arg ($action->inArguments) {
  695. my $val = shift;
  696. my $type = $service->getArgumentType($arg);
  697. push @inArgs, SOAP::Data->type($type => $val)->name($arg->name);
  698. }
  699. return UPnP::ControlPoint::ActionResult->new(
  700. Action => $action,
  701. Service => $service,
  702. SOM => $proxy->call($method => @inArgs));
  703. }
  704. # ----------------------------------------------------------------------
  705. package UPnP::ControlPoint::ActionResult;
  706. use strict;
  707. use SOAP::Lite;
  708. use HTML::Entities ();
  709. use Carp;
  710. use vars qw($AUTOLOAD);
  711. sub new {
  712. my($class, %args) = @_;
  713. my $som = $args{SOM};
  714. my $self = bless {
  715. _som => $som,
  716. }, $class;
  717. unless (defined($som->fault())) {
  718. for my $out ($args{Action}->outArguments) {
  719. my $name = $out->name;
  720. my $data = $som->match('/Envelope/Body//' . $name)->dataof();
  721. if ($data) {
  722. my $type = $args{Service}->getArgumentType($out);
  723. $data->type($type);
  724. if ($type eq 'string') {
  725. $self->{_results}->{$name} = HTML::Entities::decode(
  726. $data->value);
  727. }
  728. else {
  729. $self->{_results}->{$name} = $data->value;
  730. }
  731. }
  732. }
  733. }
  734. return $self;
  735. }
  736. sub isSuccessful {
  737. my $self = shift;
  738. return !defined($self->{_som}->fault());
  739. }
  740. sub getValue {
  741. my $self = shift;
  742. my $name = shift;
  743. if (defined($self->{_results})) {
  744. return $self->{_results}->{$name};
  745. }
  746. return undef;
  747. }
  748. sub AUTOLOAD {
  749. my $self = shift;
  750. my $method = $AUTOLOAD;
  751. $method =~ s/.*:://;
  752. return if $method eq 'DESTROY';
  753. return $self->{_som}->$method(@_);
  754. }
  755. # ----------------------------------------------------------------------
  756. package UPnP::ControlPoint::Search;
  757. use strict;
  758. sub new {
  759. my($class, %args) = @_;
  760. return bless {
  761. _callback => $args{Callback},
  762. _type => $args{Type},
  763. _udn => $args{UDN},
  764. _friendlyName => $args{FriendlyName},
  765. }, $class;
  766. }
  767. sub _passesFilter {
  768. my $self = shift;
  769. my $device = shift;
  770. my $type = $self->{_type};
  771. my $name = $self->{_friendlyName};
  772. my $udn = $self->{_udn};
  773. if ((!defined($type) || ($type eq $device->deviceType()) ||
  774. ($type eq 'ssdp:all')) &&
  775. (!defined($name) || ($name eq $device->friendlyName())) &&
  776. (!defined($udn) || ($udn eq $device->udn()))) {
  777. return 1;
  778. }
  779. return 0;
  780. }
  781. sub deviceAdded {
  782. my $self = shift;
  783. my $device = shift;
  784. if ($self->_passesFilter($device) &&
  785. !$self->{_devices}->{$device}) {
  786. &{$self->{_callback}}($self, $device, 'deviceAdded');
  787. $self->{_devices}->{$device}++;
  788. }
  789. }
  790. sub deviceRemoved {
  791. my $self = shift;
  792. my $device = shift;
  793. if ($self->_passesFilter($device) &&
  794. $self->{_devices}->{$device}) {
  795. &{$self->{_callback}}($self, $device, 'deviceRemoved');
  796. delete $self->{_devices}->{$device};
  797. }
  798. }
  799. # ----------------------------------------------------------------------
  800. package UPnP::ControlPoint::Subscription;
  801. use strict;
  802. use Time::HiRes;
  803. use Scalar::Util qw(weaken);
  804. use Carp;
  805. sub new {
  806. my($class, %args) = @_;
  807. my $self = bless {
  808. _callback => $args{Callback},
  809. _sid => $args{SID},
  810. _timeout => $args{Timeout},
  811. _startTime => Time::HiRes::time(),
  812. _eventSubURL => $args{EventSubURL},
  813. }, $class;
  814. weaken($self->{_service} = $args{Service});
  815. return $self;
  816. }
  817. sub SID {
  818. my $self = shift;
  819. return $self->{_sid};
  820. }
  821. sub timeout {
  822. my $self = shift;
  823. return $self->{_timeout};
  824. }
  825. sub expired {
  826. my $self = shift;
  827. if ($self->{_timeout} eq 'INFINITE') {
  828. return 0;
  829. }
  830. my $now = Time::HiRes::time();
  831. if ($now - $self->{_startTime} > $self->{_timeout}) {
  832. return 1;
  833. }
  834. return 0;
  835. }
  836. sub renew {
  837. my $self = shift;
  838. my $timeout = shift;
  839. my $url = $self->{_eventSubURL};
  840. my $request = HTTP::Request->new('SUBSCRIBE',
  841. "$url");
  842. $request->header('SID', $self->{_sid});
  843. $request->header('Timeout',
  844. 'Second-' . defined($timeout) ? $timeout : 'infinite');
  845. my $ua = LWP::UserAgent->new(timeout => 20);
  846. my $response = $ua->request($request);
  847. if ($response->is_success) {
  848. $timeout = $response->header('Timeout');
  849. if ($timeout =~ /^Second-(\d+)$/) {
  850. $timeout = $1;
  851. }
  852. $self->{_timeout} = $timeout;
  853. $self->{_startTime} = Time::HiRes::time();
  854. }
  855. else {
  856. carp("Renewal of subscription failed with error: " .
  857. $response->code . " " . $response->message);
  858. }
  859. return $self;
  860. }
  861. sub unsubscribe {
  862. my $self = shift;
  863. if ($self->{_service}) {
  864. $self->{_service}->unsubscribe($self);
  865. }
  866. }
  867. sub propChange {
  868. my $self = shift;
  869. my %properties = @_;
  870. if ($self->{_service}) {
  871. &{$self->{_callback}}($self->{_service}, %properties);
  872. }
  873. }
  874. 1;
  875. __END__
  876. =head1 NAME
  877. UPnP::ControlPoint - A UPnP ControlPoint implementation.
  878. =head1 SYNOPSIS
  879. use UPnP::ControlPoint;
  880. my $cp = UPnP::ControlPoint->new;
  881. my $search = $cp->searchByType("urn:schemas-upnp-org:device:TestDevice:1",
  882. \&callback);
  883. $cp->handle;
  884. sub callback {
  885. my ($search, $device, $action) = @_;
  886. if ($action eq 'deviceAdded') {
  887. print("Device: " . $device->friendlyName . " added. Device contains:\n");
  888. for my $service ($device->services) {
  889. print("\tService: " . $service->serviceType . "\n");
  890. }
  891. }
  892. elsif ($action eq 'deviceRemoved') {
  893. print("Device: " . $device->friendlyName . " removed\n");
  894. }
  895. }
  896. =head1 DESCRIPTION
  897. Implements a UPnP ControlPoint. This module implements the various
  898. aspects of the UPnP architecture from the standpoint of a ControlPoint:
  899. =over 4
  900. =item 1. Discovery
  901. A ControlPoint can be used to actively search for devices and services
  902. on a local network or listen for announcements as devices enter and
  903. leave the network. The protocol used for discovery is the Simple
  904. Service Discovery Protocol (SSDP).
  905. =item 2. Description
  906. A ControlPoint can get information describing devices and
  907. services. Devices can be queried for services and vendor-specific
  908. information. Services can be queried for actions and state variables.
  909. =item 3. Control
  910. A ControlPoint can invoke actions on services and poll for state
  911. variable values. Control-related calls are generally made using the
  912. Simple Object Access Protocol (SOAP).
  913. =item 4. Eventing
  914. ControlPoints can listen for events describing state changes in
  915. devices and services. Subscription requests and state change events
  916. are generally sent using the General Event Notification Architecture
  917. (GENA).
  918. =back
  919. Since the UPnP architecture leverages several existing protocols such
  920. as TCP, UDP, HTTP and SOAP, this module requires several Perl modules
  921. that implement these protocols. These include
  922. L<IO::Socket::INET|IO::Socket::INET>,
  923. L<LWP::UserAgent|LWP::UserAgent>,
  924. L<HTTP::Daemon|HTTP::Daemon> and
  925. C<SOAP::Lite> (L<http://www.soaplite.com>).
  926. =head1 METHODS
  927. =head2 UPnP::ControlPoint
  928. A ControlPoint implementor will generally create a single instance of
  929. the C<UPnP::ControlPoint> class (though more than one can exist within
  930. a process assuming that they have been set up to avoid port
  931. conflicts).
  932. =over 4
  933. =item new ( [ARGS] )
  934. Creates a C<UPnP::ControlPoint> object. Accepts the following
  935. key-value pairs as optional arguments (default values are listed
  936. below):
  937. SearchPort Port on which search requests are received 8008
  938. SubscriptionPort Port on which event notification are received 8058
  939. SubscriptionURL URL on which event notification are received /eventSub
  940. MaxWait Max wait before search responses should be sent 3
  941. While this call creates the sockets necessary for the ControlPoint to
  942. function, the ControlPoint is not active until its sockets are
  943. actually serviced, either by invoking the C<handle>
  944. method or by externally selecting using the ControlPoint's
  945. C<sockets> and invoking the
  946. C<handleOnce> method as each becomes ready for
  947. reading.
  948. =item sockets
  949. Returns a list of sockets that need to be serviced for the
  950. ControlPoint to correctly function. This method is generally used in
  951. conjunction with the C<handleOnce> method by users who want to run
  952. their own C<select> loop. This list of sockets should be selected for
  953. reading and C<handleOnce> is invoked for each socket as it beoms ready
  954. for reading.
  955. =item handleOnce ( SOCKET )
  956. Handles the function of reading from a ControlPoint socket when it is
  957. ready (as indicated by a C<select>). This method is used by developers
  958. who want to run their own C<select> loop.
  959. =item handle
  960. Takes over handling of all ControlPoint sockets. Runs its own
  961. C<select> loop, handling individual sockets as they become available
  962. for reading. Returns only when a call to
  963. C<stopHandling> is made (generally from a
  964. ControlPoint callback or a signal handler). This method is an
  965. alternative to using the C<sockets> and
  966. C<handleOnce> methods.
  967. =item stopHandling
  968. Ends the C<select> loop run by C<handle>. This method is generally
  969. invoked from a ControlPoint callback or a signal handler.
  970. =item searchByType ( TYPE, CALLBACK )
  971. Used to start a search for devices on the local network by device or
  972. service type. The C<TYPE> parameter is a string inidicating a device
  973. or service type. Specifically, it is the string that will be put into
  974. the C<ST> header of the SSDP C<M-SEARCH> request that is sent out. The
  975. C<CALLBACK> parameter is a code reference to a callback that is
  976. invoked when a device matching the search criterion is found (or a
  977. SSDP announcement is received that such a device is entering or
  978. leaving the network). This method returns a
  979. L<C<UPnP::ControlPoint::Search>|/UPnP::ControlPoint::Search> object.
  980. The arguments to the C<CALLBACK> are the search object, the device
  981. that has been found or newly added to or removed from the network, and
  982. an action string which is one of 'deviceAdded' or 'deviceRemoved'. The
  983. callback is invoked separately for each device that matches the search
  984. criterion.
  985. sub callback {
  986. my ($search, $device, $action) = @_;
  987. if ($action eq 'deviceAdded') {
  988. print("Device: " . $device->friendlyName . " added.\n");
  989. }
  990. elsif ($action eq 'deviceRemoved') {
  991. print("Device: " . $device->friendlyName . " removed\n");
  992. }
  993. }
  994. =item searchByUDN ( UDN, CALLBACK )
  995. Used to start a search for devices on the local network by Unique
  996. Device Name (UDN). Similar to C<searchByType>, this method sends
  997. out a SSDP C<M-SEARCH> request with a C<ST> header of
  998. C<upnp:rootdevice>. All responses to the search (and subsequent SSDP
  999. announcements to the network) are filtered by the C<UDN> parameter
  1000. before resulting in C<CALLBACK> invocation. The parameters to the
  1001. callback are the same as described in C<searchByType>.
  1002. =item searchByFriendlyName ( NAME, CALLBACK )
  1003. Used to start a search for devices on the local network by device
  1004. friendy name. Similar to C<searchByType>, this method sends out a
  1005. SSDP C<M-SEARCH> request with a C<ST> header of
  1006. C<upnp:rootdevice>. All responses to the search (and subsequent SSDP
  1007. announcements to the network) are filtered by the C<NAME> parameter
  1008. before resulting in C<CALLBACK> invocation. The parameters to the
  1009. callback are the same as described in C<searchByType>.
  1010. =item stopSearch ( SEARCH )
  1011. The C<SEARCH> parameter is a
  1012. L<C<UPnP::ControlPoint::Search>|/UPnP::ControlPoint::Search> object
  1013. returned by one of the search methods. This method stops forwarding
  1014. SSDP events that match the search criteria of the specified search.
  1015. =back
  1016. =head2 UPnP::ControlPoint::Device
  1017. A C<UPnP::ControlPoint::Device> is generally obtained using one of the
  1018. L<C<UPnP::ControlPoint>|/UPnP::ControlPoint> search methods and should
  1019. not be directly instantiated.
  1020. =over 4
  1021. =item deviceType
  1022. =item friendlyName
  1023. =item manufacturer
  1024. =item manufacturerURL
  1025. =item modelDescription
  1026. =item modelName
  1027. =item modelNumber
  1028. =item modelURL
  1029. =item serialNumber
  1030. =item UDN
  1031. =item presentationURL
  1032. =item UPC
  1033. Properties received from the device's description document. The
  1034. returned values are all strings.
  1035. =item location
  1036. A URI representing the location of the device on the network.
  1037. =item parent
  1038. The parent device of this device. The value C<undef> if this device
  1039. is a root device.
  1040. =item children
  1041. A list of child devices. The empty list if the device has no
  1042. children.
  1043. =item services
  1044. A list of L<C<UPnP::ControlPoint::Service>|/UPnP::ControlPoint::Service>
  1045. objects corresponding to the services implemented by this device.
  1046. =item getService ( ID )
  1047. If the device implements a service whose serviceType or serviceId is
  1048. equal to the C<ID> parameter, the corresponding
  1049. L<C<UPnP::ControlPoint::Service>|/UPnP::ControlPoint::Service> object
  1050. is returned. Otherwise returns C<undef>.
  1051. =back
  1052. =head2 UPnP::ControlPoint::Service
  1053. A C<UPnP::ControlPoint::Service> is generally obtained from a
  1054. L<C<UPnP::ControlPoint::Device>|/UPnP::ControlPoint::Device> object
  1055. using the C<services> or C<getServiceById> methods. This class should
  1056. not be directly instantiated.
  1057. =over 4
  1058. =item serviceType
  1059. =item serviceId
  1060. =item SCPDURL
  1061. =item controlURL
  1062. =item eventSubURL
  1063. Properties corresponding to the service received from the containing
  1064. device's description document. The returned values are all strings
  1065. except for the URL properties, which are absolute URIs.
  1066. =item actions
  1067. A list of L<C<UPnP::Common::Action>|/UPnP::Common::Action>
  1068. objects corresponding to the actions implemented by this service.
  1069. =item getAction ( NAME )
  1070. Returns the
  1071. L<C<UPnP::Common::Action>|/UPnP::Common::Action> object
  1072. corresponding to the action specified by the C<NAME> parameter.
  1073. Returns C<undef> if no such action exists.
  1074. =item stateVariables
  1075. A list of
  1076. L<C<UPnP::Common::StateVariable>|/UPnP::Common::StateVariable>
  1077. objects corresponding to the state variables implemented by this
  1078. service.
  1079. =item getStateVariable ( NAME )
  1080. Returns the
  1081. L<C<UPnP::Common::StateVariable>|/UPnP::Common::StateVariable>
  1082. object corresponding to the state variable specified by the C<NAME>
  1083. parameter. Returns C<undef> if no such state variable exists.
  1084. =item controlProxy
  1085. Returns a
  1086. L<C<UPnP::ControlPoint::ControlProxy>|/UPnP::ControlPoint::ControlProxy>
  1087. object that can be used to invoke actions on the service.
  1088. =item queryStateVariable ( NAME )
  1089. Generates a SOAP call to the remote service to query the value of the
  1090. state variable specified by C<NAME>. Returns the value of the
  1091. variable. Returns C<undef> if no such state variable exists or the
  1092. variable is not evented.
  1093. =item subscribe ( CALLBACK )
  1094. Registers an event subscription with the remote service. The code
  1095. reference specied by the C<CALLBACK> parameter is invoked when GENA
  1096. events are received from the service. This call returns a
  1097. L<C<UPnP::ControlPoint::Subscription>|/UPnP::ControlPoint::Subscription>
  1098. object corresponding to the subscription. The subscription can later
  1099. be canceled using the C<unsubscribe> method. The parameters to the
  1100. callback are the service object and a list of name-value pairs for all
  1101. of the state variables whose values are included in the corresponding
  1102. GENA event:
  1103. sub eventCallback {
  1104. my ($service, %properties) = @_;
  1105. print("Event received for service " . $service->serviceId . "\n");
  1106. while (my ($key, $val) = each %properties) {
  1107. print("\tProperty ${key}'s value is " . $val . "\n");
  1108. }
  1109. }
  1110. =item unsubscribe ( SUBSCRIPTION )
  1111. Unsubscribe from a service. This method takes the
  1112. L</UPnP::ControlPoint::Subscription>
  1113. object returned from a previous call to C<subscribe>. This method
  1114. is equivalent to calling the C<unsubscribe> method on the subscription
  1115. object itself and is included for symmetry and convenience.
  1116. =back
  1117. =head2 UPnP::Common::Action
  1118. A C<UPnP::Common::Action> is generally obtained from a
  1119. L<C<UPnP::ControlPoint::Service>|/UPnP::ControlPoint::Service> object
  1120. using its C<actions> or C<getAction> methods. It corresponds to an
  1121. action implemented by the service. Action information is retrieved
  1122. from the service's description document. This class should not be
  1123. directly instantiated.
  1124. =over 4
  1125. =item name
  1126. The name of the action returned as a string.
  1127. =item retval
  1128. A L<C<UPnP::Common::Argument>|/UPnP::Common::Argument> object that
  1129. corresponds to the action argument that is specified in the service
  1130. description document as the return value for this action. Returns
  1131. C<undef> if there is no specified return value.
  1132. =item arguments
  1133. A list of L<C<UPnP::Common::Argument>|/UPnP::Common::Argument> objects
  1134. corresponding to the arguments of the action.
  1135. =item inArguments
  1136. A list of L<C<UPnP::Common::Argument>|/UPnP::Common::Argument> objects
  1137. corresponding to the input arguments of the action.
  1138. =item outArguments
  1139. A list of L<C<UPnP::Common::Argument>|/UPnP::Common::Argument> objects
  1140. corresponding to the output arguments of the action.
  1141. =back
  1142. =head2 UPnP::Common::Argument
  1143. A C<UPnP::Common::Argument> is generally obtained from a
  1144. L<C<UPnP::Common::Action>|/UPnP::Common::Action> object using its
  1145. C<arguments>, C<inArguments> or C<outArguments> methods. An instance
  1146. of this class corresponds to an argument of a service action, as
  1147. specified in the service's description document. This class should not
  1148. be directly instantiated.
  1149. =over 4
  1150. =item name
  1151. The name of the argument returned as a string.
  1152. =item relatedStateVariable
  1153. The name of the related state variable (which can be used to find the
  1154. type of the argument) returned as a string.
  1155. =back
  1156. =head2 UPnP::Common::StateVariable
  1157. A C<UPnP::Common::StateVariable> is generally obtained from a
  1158. L<C<UPnP::ControlPoint::Service>|/UPnP::ControlPoint::Service> object
  1159. using its C<stateVariables> or C<getStateVariable> methods. It
  1160. corresponds to a state variable implemented by the service. State
  1161. variable information is retrieved from the service's description
  1162. document. This class should not be directly instantiated.
  1163. =over 4
  1164. =item name
  1165. The name of the state variable returned as a string.
  1166. =item evented
  1167. Whether the state variable is evented or not.
  1168. =item type
  1169. The listed UPnP type of the state variable returned as a string.
  1170. =item SOAPType
  1171. The corresponding SOAP type of the state variable returned as a
  1172. string.
  1173. =back
  1174. =head2 UPnP::ControlPoint::ControlProxy
  1175. A proxy that can be used to invoke actions on a UPnP service. An
  1176. instance of this class is generally obtained from the C<controlProxy>
  1177. method of the corresponding
  1178. L<C<UPnP::ControlPoint::Service>|/UPnP::ControlPoint::Service>
  1179. object. This class should not be directly instantiated.
  1180. An instance of this class is a wrapper on a C<SOAP::Lite> proxy. An
  1181. action is invoked as if it were a method of the proxy
  1182. object. Parameters to the action should be passed to the method. They
  1183. will automatically be coerced to the correct type. For example, to
  1184. invoke the C<Browse> method on a UPnP ContentDirectory service to get
  1185. the children of the root directory, one would say:
  1186. my $proxy = $service->controlProxy;
  1187. my $result = $proxy->Browse('0', 'BrowseDirectChildren', '*', 0, 0, "");
  1188. The result of a action invocation is an instance of the
  1189. L<C<UPnP::ControlPoint::ActionResult>|/UPnP::ControlPoint::ActionResult>
  1190. class.
  1191. =head2 UPnP::ControlPoint::ActionResult
  1192. An instance of this class is returned from an action invocation made
  1193. through a
  1194. L<C<UPnP::ControlPoint::ControlProxy>|/UPnP::ControlPoint::ControlProxy>
  1195. object. It is a loose wrapper on the C<SOAP::SOM> object returned from
  1196. the call made through the C<SOAP::Lite> module. All methods not
  1197. recognized by this class will be forwarded directly to the
  1198. C<SOAP::SOM> class. This class should not be directly instantiated.
  1199. =over 4
  1200. =item isSuccessful
  1201. Was the invocation successful or did it result in a fault.
  1202. =item getValue ( NAME )
  1203. Gets the value of an out argument of the action invocation. The
  1204. C<NAME> parameter specifies which out argument value should be
  1205. returned. The type of the returned value depends on the type
  1206. specified in the service description file.
  1207. =back
  1208. =head2 UPnP::ControlPoint::Search
  1209. A C<UPnP::ControlPoint::Search> object is returned from any successful
  1210. calls to the L<C<UPnP::ControlPoint>|/UPnP::ControlPoint> search
  1211. methods. It has no methods of its own, but can be used as a token to
  1212. pass to any subsequent C<stopSearch> calls. This class should not be
  1213. directly instantiated.
  1214. =head2 UPnP::ControlPoint::Subscription
  1215. A C<UPnP::ControlPoint::Search> object is returned from any successful
  1216. calls to the
  1217. L<C<UPnP::ControlPoint::Service>|/UPnP::ControlPoint::Service>
  1218. C<subscribe> method. This class should not be directly instantiated.
  1219. =over 4
  1220. =item SID
  1221. The subscription ID returned from the remote service, returned as a
  1222. string.
  1223. =item timeout
  1224. The timeout value returned from the remote service, returned as a
  1225. number.
  1226. =item expired
  1227. Has the subscription expired yet?
  1228. =item renew
  1229. Renews a subscription with the remote service by sending a GENA
  1230. subscription event.
  1231. =item unsubscribe
  1232. Unsubscribes from the remote service by sending a GENA unsubscription
  1233. event.
  1234. =back
  1235. =head1 SEE ALSO
  1236. UPnP documentation and resources can be found at L<http://www.upnp.org>.
  1237. The C<SOAP::Lite> module can be found at L<http://www.soaplite.com>.
  1238. UPnP ControlPoint implementations in other languages include the UPnP
  1239. SDK for Linux (L<http://upnp.sourceforge.net/>), Cyberlink for Java
  1240. (L<http://www.cybergarage.org/net/upnp/java/index.html>) and C++
  1241. (L<http://sourceforge.net/projects/clinkcc/>), and the Microsoft UPnP
  1242. SDK
  1243. (L<http://msdn.microsoft.com/library/default.asp?url=/library/en-us/upnp/upnp/universal_plug_and_play_start_page.asp>).
  1244. =head1 AUTHOR
  1245. Vidur Apparao (vidurapparao@users.sourceforge.net)
  1246. =head1 COPYRIGHT AND LICENSE
  1247. Copyright (C) 2004 by Vidur Apparao
  1248. This library is free software; you can redistribute it and/or modify
  1249. it under the same terms as Perl itself, either Perl version 5.8 or,
  1250. at your option, any later version of Perl 5 you may have available.
  1251. =cut
  1252. # Local Variables:
  1253. # tab-width:4
  1254. # indent-tabs-mode:t
  1255. # End: