File Coverage

blib/lib/Device/Inverter/KOSTAL/PIKO.pm
Criterion Covered Total %
statement 33 76 43.4
branch 0 22 0.0
condition n/a
subroutine 11 17 64.7
pod 0 6 0.0
total 44 121 36.3


line stmt bran cond sub pod time code
1             package Device::Inverter::KOSTAL::PIKO;
2              
3 1     1   103920 use strict;
  1         3  
  1         32  
4 1     1   663 use utf8;
  1         15  
  1         5  
5 1     1   33 use warnings;
  1         2  
  1         48  
6              
7             our $VERSION = '0.1';
8              
9 1     1   572 use Mouse;
  1         29380  
  1         4  
10 1     1   383 use Mouse::Util::TypeConstraints;
  1         3  
  1         4  
11 1     1   105 use Carp qw(carp confess croak);
  1         3  
  1         65  
12 1     1   635 use Params::Validate qw(validate_pos);
  1         9597  
  1         83  
13 1     1   9 use Scalar::Util qw(openhandle);
  1         2  
  1         50  
14 1     1   611 use URI;
  1         6889  
  1         38  
15 1     1   566 use namespace::clean -except => 'meta';
  1         9670  
  1         6  
16              
17             class_type('URI');
18             coerce URI => from Str => via { URI->new(shift) };
19              
20             has configfile => (
21             is => 'rw',
22             isa => 'Str',
23             default => sub {
24             require File::HomeDir;
25             require File::Spec;
26             File::Spec->catfile( File::HomeDir->my_home, '.pikorc' );
27             }
28             );
29              
30             # Define standard attributes which are read from ~/.pikorc if needed:
31             for (
32             [
33             host => (
34             last_resort => sub {
35             'piko';
36             },
37             ),
38             ],
39             [
40             status_url => (
41             coerce => 1,
42             isa => 'URI',
43             last_resort => sub {
44             my $self = shift;
45             defined( my $host = $self->host ) or return;
46             "http://$host/";
47             },
48             )
49             ],
50             [
51             logdata_url => (
52             coerce => 1,
53             isa => 'URI',
54             last_resort => sub {
55             my $self = shift;
56             defined( my $status_url = $self->status_url ) or return;
57             ( my $logdata_url = $status_url->clone )->path('/LogDaten.dat');
58             $logdata_url;
59             },
60             )
61             ],
62             ['number'],
63             [
64             password => (
65             last_resort => sub {
66             my $self = shift;
67             require Net::Netrc;
68             my $pvserver = Net::Netrc->lookup( $self->host ) or return;
69             $pvserver->password;
70             },
71             ),
72             ],
73             [ time_offset => ( isa => 'Int', ) ],
74             [
75             username => (
76             last_resort => sub {
77             'pvserver';
78             },
79             )
80             ]
81             )
82             {
83             my ( $attr, %spec ) = @$_;
84             my $last_resort = delete $spec{last_resort};
85             my $has_attr = "has_$attr";
86              
87             # Include defaults in spec:
88             %spec = (
89             is => 'rw',
90             isa => 'Str',
91             lazy => 1,
92             default => sub {
93             my $self = shift;
94             $self->read_configfile;
95             return $self->$attr if $self->$has_attr;
96             if ( defined $last_resort
97             && defined( my $value = $last_resort->($self) ) )
98             {
99             $self->$attr($value);
100             return $value;
101             }
102             confess("$attr not set");
103             },
104             predicate => $has_attr,
105             %spec
106             );
107              
108             has $attr => %spec;
109             }
110              
111             sub configure {
112 0     0 0   my ( $self, $config_subhash ) = @_;
113 0           while ( my ( $attr, $data ) = each %$config_subhash ) {
114 0           my $has_attr = "has_$attr";
115 0 0         $self->$attr($data) unless $self->$has_attr;
116             }
117             }
118              
119             sub fetch_logdata {
120 0     0 0   my $self = shift;
121 0           $self->load( \$self->get( logdata_url => @_ ) );
122             }
123              
124             sub get {
125 0     0 0   my ( $self, $what, %args ) = @_;
126 0           my $url = $self->$what;
127 0           require HTTP::Request;
128 0           require LWP::UserAgent;
129 0           ( my $request = HTTP::Request->new( GET => $url ) )
130             ->authorization_basic( $self->username, $self->password );
131 0           my $ua = LWP::UserAgent->new;
132 0           local *STDERR = \*STDERR;
133 0 0         if ( $args{progress_to} ) {
134 0           open STDERR, '>&', $args{progress_to};
135 0           $ua->show_progress(1);
136             }
137 0           my $response = $ua->request($request);
138 0 0         croak( "Could not fetch <$url>: " . $response->status_line )
139             unless $response->is_success;
140 0           $response->decoded_content;
141             }
142              
143             sub get_current_status {
144 0     0 0   my $self = shift;
145 0           require Device::Inverter::KOSTAL::PIKO::Status;
146 0           Device::Inverter::KOSTAL::PIKO::Status->new(
147             $self->get( status_url => @_ ) );
148             }
149              
150             sub load {
151 0     0 0   my $self = shift;
152 0           my ($source) = validate_pos( @_, 1 );
153 0           my %param = ( inverter => $self );
154 0 0         unless ( ref $source ) { # String => filename
    0          
155 0 0         open $param{fh}, '<:crlf', $param{filename} = $source
156             or croak(qq(Cannot open file "$source" for reading: $!));
157             }
158 0           elsif ( openhandle $source ) {
159 0           binmode( $source, ':crlf' );
160 0           $param{fh} = $source;
161             }
162             else {
163 0 0         open $param{fh}, '<:crlf', $source
164             or croak(qq(Cannot open reference for reading: $!));
165             }
166 0           require Device::Inverter::KOSTAL::PIKO::File;
167 0           Device::Inverter::KOSTAL::PIKO::File->new(%param);
168             }
169              
170             sub read_configfile {
171 0     0 0   my $self = shift;
172 0           my $configfile = $self->configfile;
173 0 0         carp(qq(Config file "$configfile" not found)) unless -e $configfile;
174 0           require Config::INI::Reader;
175 0           my $config_hash = Config::INI::Reader->read_file($configfile);
176              
177 0 0         if ( $self->has_number ) {
178 0 0         if ( defined( my $specific_config = $config_hash->{ $self->number } ) )
179             {
180 0           $self->configure($specific_config);
181             }
182             }
183 0 0         if ( defined( my $general_config = $config_hash->{_} ) ) {
184 0           $self->configure($general_config);
185             }
186             }
187              
188             __PACKAGE__->meta->make_immutable;
189 1     1   1908 no Mouse;
  1         3  
  1         8  
190              
191             1;
192              
193             __END__
194              
195             =encoding UTF-8
196              
197             =head1 NAME
198              
199             Device::Inverter::KOSTAL::PIKO - represents a KOSTAL PIKO DC/AC converter
200              
201             =head1 SYNOPSIS
202              
203             use Device::Inverter::KOSTAL::PIKO;
204              
205             my $piko = Device::Inverter::KOSTAL::PIKO->new( time_offset => 1309160816 );
206             my $file = $piko->load($filename_or_handle_or_ref_to_data);
207             say $_->timestamp for $file->logdata;
208              
209             =head1 SAMPLE ~/.pikorc
210              
211             [255]
212             host = piko
213             time_offset = 1309160816
214              
215             =head1 METHODS
216              
217             =head1 host
218              
219             =head1 logdata_url
220              
221             =head1 status_url
222              
223             =head1 fetch_logdata
224              
225             =head1 get_current_status
226              
227             Fetch current device status and return it as
228             L<Device::Inverter::KOSTAL::PIKO::Status> object.
229              
230             =head1 read_configfile
231              
232             =head1 AUTHOR
233              
234             Martin Sluka, C<< <fany at cpan.org> >>
235              
236             =head1 BUGS
237              
238             Please report any bugs or feature requests at
239             L<https://github.com/fany/Device-Inverter-KOSTAL-PIKO/issues>.
240             L<Pull requests|https://github.com/fany/Device-Inverter-KOSTAL-PIKO/pulls>
241             are also welcome.
242             I will be notified, and then you'll
243             automatically be notified of progress on your bug as I make changes.
244              
245             =head1 SUPPORT
246              
247             You can find documentation for this module with the perldoc command.
248              
249             perldoc Device::Inverter::KOSTAL::PIKO
250              
251             You can also look for information at:
252              
253             =over 4
254              
255             =item * RT: CPAN's request tracker (report bugs here)
256              
257             L<https://rt.cpan.org/NoAuth/Bugs.html?Dist=Device-Inverter-KOSTAL-PIKO>
258              
259             =item * CPAN Ratings
260              
261             L<https://cpanratings.perl.org/d/Device-Inverter-KOSTAL-PIKO>
262              
263             =item * Search CPAN
264              
265             L<https://metacpan.org/release/Device-Inverter-KOSTAL-PIKO>
266              
267             =back
268              
269             =head1 LICENSE AND COPYRIGHT
270              
271             Copyright 2012–2021 Martin Sluka.
272              
273             This program is free software; you can redistribute it and/or modify it
274             under the terms of either: the GNU General Public License as published
275             by the Free Software Foundation; or the Artistic License.
276              
277             See https://dev.perl.org/licenses/ for more information.