diff --git a/HASS.pm b/HASS.pm new file mode 100644 index 0000000..7b54b85 --- /dev/null +++ b/HASS.pm @@ -0,0 +1,154 @@ +package Plugins::Assistant::HASS; + +use strict; +use JSON::XS::VersionOneAndTwo; +use threads::shared; + +use Slim::Networking::SimpleAsyncHTTP; +use Slim::Utils::Log; +use Slim::Utils::Prefs; + +my $log = logger('plugin.assistant'); + +my $url; +my $cache; + +my $prefs = preferences('plugin.assistant'); + + +sub init { + ($cache) = @_; + + $url = $prefs->get('connect'); + + #$pass = $prefs->get('pass'); +} + + +sub testHass { + + # TODO: + # empty api request + # if response message = "API running." then return true + # show in settings +} + + +sub getEntities { + my ( $client, $cb, $params, $args ) = @_; + + our $result :shared = []; + our $counter :shared = 0; + + if (defined $args->{'entity_ids'}) { + foreach my $entity_id(@{$args->{'entity_ids'}}) { + + $counter++; + Plugins::Assistant::HASS::getEntity( + $client, + sub { + my $entity = shift; + if (defined $entity) { + push @$result, $entity; + } + $counter--; + if ($counter <= 0) { + $cb->($result); + } + }, + $params, + { + entity_id => $entity_id, + }, + ); + } + } else { + + Plugins::Assistant::HASS::getEntity( + $client, + sub { + my $entities = shift; + foreach my $entity(@$entities) { + push @$result, $entity; + } + $cb->($result); + }, + $params, + {}, + ); + } +} + + +sub getEntity { + my ($client, $cb, $params, $args) = @_; + + my $localurl = $url.'states'; + if (defined $args->{'entity_id'}) { + $localurl = $localurl.'/'.$args->{'entity_id'}; + } + + $log->debug('Get Entity: ', $localurl); + + my $http = Slim::Networking::SimpleAsyncHTTP->new( + sub { + my $response = shift; + my $params = $response->params('params'); + my $result; + if ( $response->headers->content_type =~ /json/ ) { + $result = decode_json($response->content); + } + $cb->($result); + }, + sub { + $log->warn("Warning (".$localurl."): $_[1]"); + $cb->(); + }, + { + params => $params, + timeout => 5, + }, + ); + + $http->get( + $localurl, + 'Content-Type' => 'application/json', + 'charset' => 'UTF-8', + ); +} + + +sub toggleLightEntity { + my ($client, $cb, $params, $args) = @_; + + my $localurl = $url.'services/light/toggle'; + my $req->{'entity_id'} = $args->{'entity_id'}; + + my $http = Slim::Networking::SimpleAsyncHTTP->new( + sub { + my $response = shift; + my $params = $response->params('params'); + my $result; + if ( $response->headers->content_type =~ /json/ ) { + $result = decode_json($response->content); + } + $cb->($result); + }, + sub { + $log->error("error: $_[1]"); + $cb->(); + }, + { + timeout => 5, + }, + ); + + $http->post( + $localurl, + 'Content-Type' => 'application/json', + 'charset' => 'UTF-8', + encode_json($req) + ); +} + +1; diff --git a/HTML/EN/plugins/Assistant/html/images/icon.png b/HTML/EN/plugins/Assistant/html/images/icon.png new file mode 100644 index 0000000..04e1dad Binary files /dev/null and b/HTML/EN/plugins/Assistant/html/images/icon.png differ diff --git a/HTML/EN/plugins/Assistant/html/images/light_off.png b/HTML/EN/plugins/Assistant/html/images/light_off.png new file mode 100644 index 0000000..3129f58 Binary files /dev/null and b/HTML/EN/plugins/Assistant/html/images/light_off.png differ diff --git a/HTML/EN/plugins/Assistant/html/images/light_on.png b/HTML/EN/plugins/Assistant/html/images/light_on.png new file mode 100644 index 0000000..ad61ae9 Binary files /dev/null and b/HTML/EN/plugins/Assistant/html/images/light_on.png differ diff --git a/HTML/EN/plugins/Assistant/settings.html b/HTML/EN/plugins/Assistant/settings.html new file mode 100644 index 0000000..dab2cd8 --- /dev/null +++ b/HTML/EN/plugins/Assistant/settings.html @@ -0,0 +1,11 @@ +[% PROCESS settings/header.html %] + + [% WRAPPER setting title="PLUGIN_ASSISTANT_CONNECT" desc="PLUGIN_ASSISTANT_CONNECT_DESC" %] + + [% END %] + + [% WRAPPER setting title="PLUGIN_ASSISTANT_PASS" desc="PLUGIN_ASSISTANT_PASS_DESC" %] + + [% END %] + +[% PROCESS settings/footer.html %] diff --git a/Plugin.pm b/Plugin.pm new file mode 100644 index 0000000..7aeee48 --- /dev/null +++ b/Plugin.pm @@ -0,0 +1,203 @@ +package Plugins::Assistant::Plugin; + +use strict; +use base qw(Slim::Plugin::OPMLBased); +use JSON::XS::VersionOneAndTwo; +use threads::shared; +use feature qw(switch); + +use Slim::Utils::Log; +use Slim::Utils::Prefs; +use Slim::Utils::Strings qw(string cstring); + +use Plugins::Assistant::HASS; + +my $log = Slim::Utils::Log->addLogCategory( + { + 'category' => 'plugin.assistant', + 'defaultLevel' => 'DEBUG', + 'description' => 'PLUGIN_ASSISTANT', + } +); + +my $prefs = preferences('plugin.assistant'); + +my $cache = Slim::Utils::Cache->new('assistant', 3); + + +sub initPlugin { + my $class = shift; + + if (my $username = $prefs->get('connect')) { + $prefs->set('connect', '') if $username eq '_assistant_'; + } + + $prefs->init( + { + connect => '_assistant_' + } + ); + + Plugins::Assistant::HASS->init($cache); + + $class->SUPER::initPlugin( + feed => \&handleFeed, + tag => 'assistant', + menu => 'radios', + is_app => 1, + weight => 1, + ); + + if (main::WEBUI) { + require Plugins::Assistant::Settings; + Plugins::Assistant::Settings->new(); + } +} + +sub getDisplayName { 'PLUGIN_ASSISTANT' } + +# don't add this plugin to the Extras menu +sub playerMenu {} + + +sub handleFeed { + my ($client, $cb, $args) = @_; + + my $params = $args->{params}; + + # Only groups in first level + $args->{'onlygroups'} = 1; + + getItems($client,$cb,$params,$args); +} + + +sub getItems { + my ($client, $cb, $params, $args) = @_; + + Plugins::Assistant::HASS::getEntities( + $client, + sub { + my $entities = shift; + my $items = []; + + foreach my $entity(@$entities) { + my ($namespace, $name) = split('\.', $entity->{'entity_id'}, 2); + + my $order = 999; + if (defined $entity->{'attributes'}->{'order'}) { + $order = $entity->{'attributes'}->{'order'}; + } + + # If current entity is included in args and this is a group, + # change namespace to the namespace of all sub entities + # Note: Currently only light is supported + if ($namespace eq 'group' && $entity->{'entity_id'} eq $args->{'entity_id'}) { + $namespace = 'light'; + } + + $log->debug('Namespace: ', $namespace, ' Name: ', $name, ' - ', $order); + + if ($namespace eq 'group' && (!$entity->{'attributes'}->{'hidden'} || $entity->{'attributes'}->{'view'})) { + + # Add current to request list if all sub entities the same + # Add current entity id to args + # Note: Currently only light is supported + my $entity_ids = $entity->{'attributes'}->{'entity_id'}; + if (!grep(!/light\./, @{$entity_ids})) { + push @$entity_ids, $entity->{'entity_id'}; + } + + push @$items, + { + name => $entity->{'attributes'}->{'friendly_name'}, + order => $order, + type => 'link', + url => \&getItems, + passthrough => [ + { + entity_id => $entity->{'entity_id'}, + entity_ids => $entity_ids, + } + ] + }; + + } elsif ($namespace eq 'light' && !defined $args->{'onlygroups'}) { + + push @$items, + { + name => $entity->{'attributes'}->{'friendly_name'}, + image => 'plugins/Assistant/html/images/light_'.$entity->{'state'}.'.png', + order => $order, + type => 'link', + url => \&toggleLightEntity, + passthrough => [ + { + entity_id => $entity->{'entity_id'}, + state => $entity->{'state'}, + } + ], + nextWindow => 'refresh', + }; + + } elsif ($namespace eq 'sensor' && !defined $args->{'onlygroups'}) { + + push @$items, + { + name => $entity->{'attributes'}->{'friendly_name'}.' '.$entity->{'state'}.$entity->{'attributes'}->{'unit_of_measurement'}, + order => $order, + type => 'text', + }; + + } elsif (!defined $args->{'onlygroups'}) { + + push @$items, + { + name => $entity->{'attributes'}->{'friendly_name'}.' '.$entity->{'state'}, + order => $order, + type => 'text', + }; + + } + } + $items = [ sort { uc($a->{order}) cmp uc($b->{order}) } @$items ]; + $cb->( + { + items => $items, + } + ); + }, + $params, + { + entity_ids => $args->{'entity_ids'}, + }, + ); +} + + +sub toggleLightEntity { + my ($client, $cb, $params, $args) = @_; + + Plugins::Assistant::HASS::toggleLightEntity( + $client, + sub { + my $items = []; + + push @$items, + { + name => 'Toggled Light', + type => 'text', + showBriefly => 1, + }; + $cb->( + { + items => $items, + } + ); + }, + $params, + $args, + ); +} + +1; \ No newline at end of file diff --git a/Settings.pm b/Settings.pm new file mode 100644 index 0000000..dc1755b --- /dev/null +++ b/Settings.pm @@ -0,0 +1,25 @@ +package Plugins::Assistant::Settings; + +use strict; +use base qw(Slim::Web::Settings); + +use Slim::Utils::Prefs; + +my $prefs = preferences('plugin.assistant'); + + +sub name { + return 'PLUGIN_ASSISTANT'; +} + + +sub prefs { + return ($prefs, 'connect'); +} + + +sub page { + return 'plugins/Assistant/settings.html'; +} + +1; \ No newline at end of file diff --git a/install.xml b/install.xml new file mode 100644 index 0000000..0c6b735 --- /dev/null +++ b/install.xml @@ -0,0 +1,20 @@ + + + PLUGIN_ASSISTANT + Hans Karlinius + enabled + false + PLUGIN_ASSISTANT_DESCRIPTION + hans.karlinius@live.com + plugins/Assistant/html/images/icon.png + 517d7aef-1bfa-49a2-b4ed-b0117d997531 + Plugins::Assistant::Plugin + plugins/Assistant/settings.html + + Logitech Media Server + * + 7.6 + + 2 + 0.1 + diff --git a/strings.txt b/strings.txt new file mode 100644 index 0000000..fac259a --- /dev/null +++ b/strings.txt @@ -0,0 +1,17 @@ +PLUGIN_ASSISTANT + EN Assistant + +PLUGIN_ASSISTANT_DESCRIPTION + EN This is good + +PLUGIN_ASSISTANT_CONNECT + EN Home Assistant connect url + +PLUGIN_ASSISTANT_CONNECT_DESC + EN Should be the same as used for web access with addition of /api at the end like http://localhost:8123/api + +PLUGIN_ASSISTANT_PASS + EN Home Assistant API password + +PLUGIN_ASSISTANT_PASS_DESC + EN The password set for http request, when logging in to the webpage