ControlPoint.pm 41 KB

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