Group
Extension

Power-Outlet/lib/Power/Outlet/Hue.pm

package Power::Outlet::Hue;
use strict;
use warnings;
use Data::Dumper qw{Dumper};
use base qw{Power::Outlet::Common::IP::HTTP::JSON};

our $VERSION = '0.50';

=head1 NAME

Power::Outlet::Hue - Control and query a Philips Hue light

=head1 SYNOPSIS

  my $outlet=Power::Outlet::Hue->new(host => "mybridge", id=>1, username=>"myuser");
  print $outlet->query, "\n";
  print $outlet->on, "\n";
  print $outlet->off, "\n";

=head1 DESCRIPTION

Power::Outlet::Hue is a package for controlling and querying a light on a Philips Hue network attached bridge.

=head1 USAGE

  use Power::Outlet::Hue;
  my $lamp=Power::Outlet::Hue->new(host=>"mybridge", id=>1, username=>"myuser");
  print $lamp->on, "\n";

=head1 CONSTRUCTOR

=head2 new

  my $outlet=Power::Outlet->new(type=>"Hue", host=>"mybridge", id=>1);
  my $outlet=Power::Outlet::Hue->new(host=>"mybridge", id=>1);

=head1 PROPERTIES

=head2 id

ID for the particular light as configured in the Philips Hue Bridge

Default: 1

=cut

sub id {
  my $self      = shift;
  $self->{"id"} = shift if @_;
  $self->{"id"} = $self->_id_default unless defined $self->{"id"};
  return $self->{"id"};
}

sub _id_default {1};

=head2 resource

Resource for the particular object as presented on the Philips Hue Bridge

Default: lights

Currently supported Resources from L<https://developers.meethue.com/documentation/core-concepts>

  lights    - resource which contains all the light resources
  groups    - resource which contains all the groups
  config    - resource which contains all the configuration items
  schedules - which contains all the schedules
  scenes    - which contains all the scenes
  sensors   - which contains all the sensors
  rules     - which contains all the rules

=cut

sub resource {
  my $self            = shift;
  $self->{"resource"} = shift if @_;
  $self->{"resource"} = $self->_resource_default unless defined $self->{"resource"};
  return $self->{"resource"};
}

sub _resource_default {'lights'};

=head2 host

Sets and returns the hostname or IP address.

Default: mybridge

=cut

sub _host_default {"mybridge"};

=head2 port

Sets and returns the port number.

Default: 80

=cut

sub _port_default {"80"};

=head2 username

Sets and returns the username used for authentication with the Hue Bridge

Default: newdeveloper (Hue Emulator default)

=cut

sub username {
  my $self            = shift;
  $self->{"username"} = shift if @_;
  $self->{"username"} = $self->_username_default unless defined $self->{"username"};
  return $self->{"username"};
}

sub _username_default {"newdeveloper"};

=head2 name

Returns the configured friendly name for the device

=cut

sub _name_default { #overloaded _name_default so the name will be cached for the life of this object
  my $self = shift;
  my $url  = $self->url; #isa URI from Power::Outlet::Common::IP::HTTP
  $url->path($self->_path);
  my $res  = $self->json_request(GET => $url); #isa perl structure
  return $res->{"name"}; #isa string
}

=head1 METHODS

=cut

#head2 _path

#Builds the URL path

#cut

sub _path {
  my $self     = shift;
  my $state    = shift;
  my @state    = defined($state)          ? ($state)          : ();
  my @resource = defined($self->resource) ? ($self->resource) : (); #support undef resource just in case needed
  return join('/', '', 'api', $self->username, @resource, $self->id, @state);
}

=head2 query

Sends an HTTP message to the device to query the current state

=cut

#Response: {"identifier":null,"state":{"on":true,"bri":254,"hue":4444,"sat":254,"xy":[0.0,0.0],"ct":0,"alert":"none","effect":"none","colormode":"hs","reachable":true,"transitionTime":null},"type":"Extended color light","name":"Hue Lamp 1","modelid":"LCT001","swversion":"65003148","pointsymbol":{"1":"none","2":"none","3":"none","4":"none","5":"none","6":"none","7":"none","8":"none"}}
#Response: [{"error":{"address":"/","description":"unauthorized user","type":"1"}}]
#Response: [{"error":{"address":"/lights/333","description":"resource, /lights/333, not available","type":"3"}}]


sub query {
  my $self = shift;
  if (defined wantarray) { #scalar and list context

    #url configuration
    my $url = $self->url; #isa URI from Power::Outlet::Common::IP::HTTP
    $url->path($self->_path);

    #web request
    my $res = $self->json_request(GET => $url); #isa perl structure

    #Response is an ARRAY on error and a HASH on success
    if (ref($res) eq "HASH") {
      die("Error: (query) state does not exists")              unless exists $res->{"state"};
      die("Error: (query) state is not a hash")                unless ref($res->{"state"}) eq "HASH";
      die("Error: (query) state does not provide on property") unless exists $res->{"state"}->{"on"};
      my $state = $res->{"state"}->{"on"}; #isa boolean true/false
      return $state ? "ON" : "OFF";
    } elsif (ref($res) eq "ARRAY") {
      my $hash  = shift(@$res);
      die(sprintf(qq{Error: (query) "%s"}, $hash->{"error"}->{"description"})) if exists $hash->{"error"};
      die(sprintf("Error: (query) Unkown Error: URL: %s\n\n%s", $url, Dumper($res)));
    } else {
      die(sprintf("Error: (query) Unkown Error: URL: %s\n\n%s", $url, Dumper($res)));
    }
  } else { #void context
    return;
  }
}

=head2 on

Sends a message to the device to Turn Power ON

=cut

#Response: [{"success":{"/lights/1/state/on":true}}]
#Response: [{"error":{"address":"/","description":"unauthorized user","type":"1"}}]
#Response: [{"error":{"address":"/lights/333","description":"resource, /lights/333, not available","type":"3"}}]

sub on {
  my $self = shift;
  return $self->_call("on");
}

=head2 off

Sends a message to the device to Turn Power OFF

=cut

sub off {
  my $self = shift;
  return $self->_call("off");
}

sub _call {
  my $self    = shift;
  my $input   = shift or die;
  my $boolean = $input eq "on"  ? \1 : #JSON true
                $input eq "off" ? \0 : #JSON false
                die("Error: (_call) syntax _call('on'||'off')");

  #url configuration
  my $url     = $self->url; #isa URI from Power::Outlet::Common::IP::HTTP
  $url->path($self->_path('state'));

  #web request
  my $array   = $self->json_request(PUT => $url, {on=>$boolean}); #isa perl structure

  #error handling
  die("Error: ($input) failed to return expected JSON format") unless ref($array) eq "ARRAY";
  my $hash    = shift(@$array);
  die("Error: ($input) Failed to return expected JSON format") unless ref($hash) eq "HASH";
  die(sprintf(qq{Error: ($input) "%s"}, $hash->{"error"}->{"description"})) if exists $hash->{"error"};
  die(sprintf("Error: ($input) Unkown Error: URL: %s\n\n%s", $url, Dumper($array))) unless exists $hash->{"success"};
  my $success = $hash->{"success"};
  #state normalization
  my $key     = sprintf("/lights/%s/state/on", $self->id);
  die("Error: ($input) Unkown success state") unless exists $success->{$key};
  my $state   = $success->{$key};
  return $state ? "ON" : "OFF";
}

=head2 switch

Queries the device for the current status and then requests the opposite.

=cut

#see Power::Outlet::Common->switch

=head2 cycle

Sends messages to the device to Cycle Power (ON-OFF-ON or OFF-ON-OFF).

=cut

#see Power::Outlet::Common->cycle

=head1 BUGS

Please log on RT and send an email to the author.

=head1 SUPPORT

DavisNetworks.com supports all Perl applications including this package.

=head1 AUTHOR

  Michael R. Davis
  CPAN ID: MRDVT
  DavisNetworks.com

Thanks to Mathias Neerup manee12 at student.sdu.dk - L<https://rt.cpan.org/Ticket/Display.html?id=123965>

=head1 COPYRIGHT

Copyright (c) 2018 Michael R. Davis

This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.

The full text of the license can be found in the LICENSE file included with this module.

=head1 SEE ALSO

L<http://www.developers.meethue.com/philips-hue-api>, L<http://steveyo.github.io/Hue-Emulator/>, L<https://home-assistant.io/components/emulated_hue/>

=cut

1;


Powered by Groonga
Maintained by Kenichi Ishigaki <ishigaki@cpan.org>. If you find anything, submit it on GitHub.