File Coverage

blib/lib/App/AquariumHive.pm
Criterion Covered Total %
statement 70 214 32.7
branch 0 38 0.0
condition 0 3 0.0
subroutine 24 49 48.9
pod 0 12 0.0
total 94 316 29.7


line stmt bran cond sub pod time code
1             package App::AquariumHive;
2             BEGIN {
3 1     1   1166 $App::AquariumHive::AUTHORITY = 'cpan:GETTY';
4             }
5             # ABSTRACT: Temporary Daemon - will later be replaced by HiveHub
6             $App::AquariumHive::VERSION = '0.002';
7             our $VERSION ||= '0.000';
8              
9 1         6 use MooX qw(
10             Options
11 1     1   5 );
  1         2  
12              
13 1     1   253301 use Path::Tiny;
  1         8738  
  1         81  
14 1     1   613 use PocketIO;
  1         87938  
  1         30  
15 1     1   603 use Plack::Builder;
  1         12741  
  1         67  
16 1     1   2334 use Twiggy::Server;
  1         43585  
  1         47  
17 1     1   10 use AnyEvent;
  1         1  
  1         29  
18 1     1   599 use AnyEvent::SerialPort;
  1         33078  
  1         45  
19 1     1   2109 use AnyEvent::HTTP;
  1         11598  
  1         105  
20 1     1   1349 use File::ShareDir::ProjectDistDir;
  1         14858  
  1         11  
21 1     1   618 use File::HomeDir;
  1         3  
  1         86  
22 1     1   7 use JSON::MaybeXS;
  1         2  
  1         70  
23 1     1   5191 use DateTime;
  1         173251  
  1         58  
24 1     1   1429 use Config::INI::Reader;
  1         32325  
  1         53  
25 1     1   1817 use Config::INI::Writer;
  1         4623  
  1         51  
26 1     1   8 use Carp qw( croak );
  1         2  
  1         83  
27 1     1   7 use DDP;
  1         1  
  1         18  
28 1     1   60 use Module::Runtime qw( use_module );
  1         3  
  1         12  
29             use Module::Pluggable
30 1         10 sub_name => 'plugin_classes',
31             search_path => ['App::AquariumHive::Plugin'],
32             max_depth => 4,
33 1     1   794 require => 1;
  1         12241  
34              
35 1     1   115 use HiveJSO;
  1         2  
  1         39  
36 1     1   754 use AnyEvent::HiveJSO;
  1         566  
  1         38  
37 1     1   14 use AquariumHive::Simulator;
  1         1  
  1         33  
38 1     1   6 use App::AquariumHive::DB;
  1         1  
  1         58  
39              
40 1     1   651 use Log::Any::Adapter ('Stdout');
  1         14418  
  1         8  
41              
42             with 'App::AquariumHive::LogRole';
43              
44             sub BUILD {
45 0     0 0   my ( $self ) = @_;
46 0 0         path($self->cfg)->mkpath unless -d $self->cfg;
47             }
48              
49             option 'cfg' => (
50             is => 'ro',
51             format => 'i',
52             default => sub {
53             return path(File::HomeDir->my_home,'.aqhive')->absolute->stringify;
54             },
55             doc => 'directory for config',
56             );
57              
58             option 'port' => (
59             is => 'ro',
60             format => 'i',
61             default => '8888',
62             doc => 'port for the webserver',
63             );
64              
65             option 'simulation' => (
66             is => 'ro',
67             default => 0,
68             doc => 'Simulate Aquarium Hive hardware',
69             );
70              
71             option 'serial' => (
72             is => 'ro',
73             format => 's',
74             default => '/dev/ttyAMA0',
75             doc => 'serial port for the HiveJSO stream',
76             );
77              
78             option 'baud' => (
79             is => 'ro',
80             format => 's',
81             default => '19200',
82             doc => 'baud rate for the serial port',
83             );
84              
85             option 'agent' => (
86             is => 'ro',
87             format => 's',
88             default => 'App::AquariumHive/'.$VERSION,
89             doc => 'user agent for the web requests',
90             );
91              
92             has config_ini => (
93             is => 'lazy',
94             init_arg => undef,
95             );
96              
97             sub _build_config_ini {
98 0     0     my ( $self ) = @_;
99 0           return path($self->cfg,'aqhive.ini')->stringify;
100             }
101              
102             has config => (
103             is => 'lazy',
104             init_arg => undef,
105             );
106              
107             sub _build_config {
108 0     0     my ( $self ) = @_;
109 0 0         return {} unless -f $self->config_ini;
110 0           return Config::INI::Reader->read_file($self->config_ini);
111             }
112              
113             has db => (
114             is => 'lazy',
115             init_arg => undef,
116             );
117              
118             sub _build_db {
119 0     0     my ( $self ) = @_;
120 0           return App::AquariumHive::DB->connect($self);
121             }
122              
123             sub save_config {
124 0     0 0   my ( $self ) = @_;
125 0           Config::INI::Writer->write_file($self->config, $self->config_ini);
126             }
127              
128             has plugins => (
129             is => 'lazy',
130             init_arg => undef,
131             );
132              
133             sub _build_plugins {
134 0     0     my ( $self ) = @_;
135 0           $self->debug('Building plugins...');
136 0           my @plugins;
137 0           for my $class ($self->plugin_classes) {
138 0           $self->debug('Loading '.$class);
139 0           push @plugins, use_module($class)->new( app => $self );
140             }
141 0           for (@plugins) {
142 0 0         if ($_->can('configure')) {
143 0           $_->configure;
144             }
145             }
146 0           return \@plugins;
147             }
148              
149             has pocketio => (
150             is => 'lazy',
151             init_arg => undef,
152             );
153              
154             sub _build_pocketio {
155 0     0     my ( $self ) = @_;
156             return PocketIO->new( handler => sub {
157 0     0     $self->debug('PocketIO connect');
158 0           my $pio = shift;
159 0           for my $key (keys %{$self->pocketio_handler}) {
  0            
160 0           my @ons = @{$self->pocketio_handler->{$key}};
  0            
161             $pio->on($key, sub {
162 0           my ( $pio, $message ) = @_;
163 0           $self->debug('PocketIO incoming '.$key);
164 0           for my $sub (@ons) {
165 0           $sub->($self, $message);
166             }
167 0           });
168             }
169 0           });
170             }
171              
172             has pocketio_handler => (
173             is => 'rw',
174             init_arg => undef,
175             default => sub {{}},
176             );
177              
178             sub on_socketio {
179 0     0 0   my ( $self, $key, $sub ) = @_;
180 0 0         $self->pocketio_handler->{$key} = [] unless defined $self->pocketio_handler->{$key};
181 0           push @{$self->pocketio_handler->{$key}}, $sub;
  0            
182             }
183              
184             has data_handler => (
185             is => 'rw',
186             init_arg => undef,
187             default => sub {[]},
188             );
189              
190             sub on_data {
191 0     0 0   my ( $self, $sub ) = @_;
192 0           push @{$self->data_handler}, $sub;
  0            
193             }
194              
195             has simulator => (
196             is => 'lazy',
197             init_arg => undef,
198             );
199              
200             sub _build_simulator {
201 0     0     my ( $self ) = @_;
202 0           return AquariumHive::Simulator->new;
203             }
204              
205             has uart => (
206             is => 'lazy',
207             init_arg => undef,
208             );
209              
210             sub _build_uart {
211 0     0     my ( $self ) = @_;
212 0           my $uart;
213 0 0         if ($self->simulation) {
214 0           $uart = AnyEvent::Handle->new(
215             fh => $self->simulator->fh,
216             );
217             } else {
218 0           $uart = AnyEvent::SerialPort->new(
219             serial_port => [
220             $self->serial,
221             [ baudrate => $self->baud ],
222             ],
223             read_size => 1,
224             );
225             }
226             $uart->on_read(sub {
227             $_[0]->push_read(hivejso => sub {
228 0           my ( $uart, $data ) = @_;
229 0 0         if (ref $data eq 'HiveJSO::Error') {
230 0           p($data->error); p($data->garbage);
  0            
231 0           return;
232             }
233 0           my $hivejso = $data->hivejso_short;
234 0           $self->debug('HiveJSO IN '.$hivejso);
235 0 0         if ($data->has_data) {
236 0           $self->send( data => $data->data );
237 0           for my $sub (@{$self->data_handler}) {
  0            
238 0           $sub->($self, $data->data);
239             }
240             }
241 0     0     });
242 0           });
243 0           return $uart;
244             }
245              
246             sub http {
247 0     0 0   my ( $self, $method, $url, @args ) = @_;
248 0           my $cb = pop @args;
249 0           my ( %arg ) = @args;
250 0 0         $arg{headers} = {} unless defined $arg{headers};
251 0 0         $arg{headers}->{'user-agent'} = $self->agent unless defined $arg{headers}->{'user-agent'};
252 0 0         $arg{timeout} = 30 unless defined $arg{timeout};
253 0           return http_request($method, $url, %arg, $cb);
254             }
255              
256             sub send {
257 0     0 0   my ( $self, $key, $data ) = @_;
258 0 0 0       if ($self->pocketio->pool->{connections} && %{$self->pocketio->pool->{connections}}) {
  0            
259 0           my @keys = keys %{$self->pocketio->pool->{connections}};
  0            
260 0           return $self->pocketio->pool->{connections}->{$keys[0]}->sockets->emit($key,$data);
261             }
262 0           return;
263             }
264              
265             has web_root => (
266             is => 'lazy',
267             init_arg => undef,
268             );
269              
270             sub _build_web_root {
271 0     0     my ( $self ) = @_;
272 0           return path(dist_dir('AquariumHive'),'root')->absolute->realpath->stringify;
273             }
274              
275             has web_mounts => (
276             is => 'rw',
277             init_arg => undef,
278             default => sub {{}},
279             );
280              
281             sub web_mount {
282 0     0 0   my ( $self, $mount, $psgi ) = @_;
283 0           $self->web_mounts->{$mount} = $psgi;
284             }
285              
286             has tiles => (
287             is => 'rw',
288             init_arg => undef,
289             default => sub {{}},
290             );
291              
292             sub tile {
293 0     0 0   my ( $self, $key ) = @_;
294 0           return $self->tiles->{$key};
295             }
296              
297             sub add_tile {
298 0     0 0   my ( $self, $key, $tile ) = @_;
299 0           $self->tiles->{$key} = $tile;
300             }
301              
302             has web => (
303             is => 'lazy',
304             init_arg => undef,
305             );
306              
307             sub _build_web {
308 0     0     my ( $self ) = @_;
309 0           my $server = Twiggy::Server->new(
310             port => $self->port,
311             );
312             $server->register_service(builder {
313             enable sub {
314 0           my $app = shift;
315             sub {
316 0           $self->debug('Web Request '.$_[0]->{PATH_INFO});
317 0           my $res = $app->($_[0]);
318 0           return $res;
319 0           };
320 0     0     };
321 0           mount '/shutdown' => sub { exit 0 };
  0            
322 0           mount '/socket.io' => $self->pocketio;
323 0 0         for my $mount (sort { length($a) <=> length($b) || $a cmp $b } keys %{$self->web_mounts}) {
  0            
  0            
324 0           mount '/'.$mount, $self->web_mounts->{$mount};
325             }
326             mount '/tile' => sub {
327 0           my ( $tile ) = $_[0]->{PATH_INFO} =~ m!/(.*)!;
328 0 0         if ($self->tile($tile)) {
329 0 0         return [ 200, [ "Content-Type" => "application/json" ], [encode_json({
330             html => $self->tile($tile)->html,
331             $self->tile($tile)->has_js ? ( js => $self->tile($tile)->js ) : (),
332             })] ];
333             } else {
334 0           return [ 404, [ "Content-Type" => "application/json" ], [encode_json({
335             not => 'found',
336             })] ];
337             }
338 0           };
339             mount '/tiles' => sub {
340 0           return [ 200, [ "Content-Type" => "application/json" ], [encode_json([sort { $a cmp $b } keys %{$self->tiles}])] ];
  0            
  0            
341 0           };
342             mount '/' => builder {
343             enable 'Rewrite',
344 0           rules => sub { s{^/$}{/index.html}; };
  0            
345 0           enable "Plack::Middleware::Static",
346             path => qr{^/},
347             root => $self->web_root;
348 0           };
349 0           });
350 0           return $server;
351             }
352              
353             sub command_aqhive {
354 0     0 0   my ( $self, $command, @args ) = @_;
355 0 0         return unless defined $command;
356 0           my $hivejso;
357 0 0         if ($self->simulation) {
358 0 0         $hivejso = HiveJSO->new(
359             unit => 'rasputin',
360             command => scalar @args ? ([ $command, @args ]) : ($command),
361             )->hivejso_short;
362             } else {
363 0 0         $hivejso = encode_json({
364             o => scalar @args ? ([ $command, @args ]) : ($command),
365             });
366             }
367 0           $self->debug('HiveJSO OUT '.$hivejso);
368 0           $self->uart->push_write($hivejso);
369             }
370              
371             sub run {
372 0     0 0   my ( $self ) = @_;
373              
374 0           $self->plugins;
375 0           $self->web;
376 0           $self->uart;
377 0           $self->db;
378              
379 0           $self->info("Starting App::AquariumHive (port ".$self->port.")...");
380              
381 0     0     my $t = AE::timer 0, 15, sub { $self->command_aqhive('data') };
  0            
382              
383 0           AE::cv->recv;
384             }
385              
386             sub run_cmd {
387 0     0 0   my ( $self, $command ) = @_;
388 0           my @lines;
389 0 0         if ($command) {
390 0           require IPC::Open3; # core
391             # autoflush STDOUT so we can see command output right away
392 0           local $| = 1;
393             # combine stdout and stderr for ease of proxying through the logger
394 0           my $pid = IPC::Open3::open3(my ($in, $out), undef, $command);
395 0           while(defined(my $line = <$out>)){
396 0           chomp($line);
397 0           push @lines, $line;
398             }
399             # zombie repellent
400 0           waitpid($pid, 0);
401 0           my $status = ($? >> 8);
402             }
403 0           return @lines;
404             }
405              
406             1;
407              
408             __END__
409              
410             =pod
411              
412             =head1 NAME
413              
414             App::AquariumHive - Temporary Daemon - will later be replaced by HiveHub
415              
416             =head1 VERSION
417              
418             version 0.002
419              
420             =head1 DESCRIPTION
421              
422             B<IN DEVELOPMENT, DO NOT USE YET>
423              
424             See L<http://aquariumhive.com/> for now.
425              
426             =head1 SUPPORT
427              
428             IRC
429              
430             Join #AquariumHive on irc.freenode.net. Highlight Getty for fast reaction :).
431              
432             Repository
433              
434             https://github.com/homehivelab/aquariumhive
435             Pull request and additional contributors are welcome
436              
437             Issue Tracker
438              
439             https://github.com/homehivelab/aquariumhive/issues
440              
441             =head1 AUTHOR
442              
443             Torsten Raudssus <torsten@raudss.us>
444              
445             =head1 COPYRIGHT AND LICENSE
446              
447             This software is copyright (c) 2014 by Torsten Raudssus.
448              
449             This is free software; you can redistribute it and/or modify it under
450             the same terms as the Perl 5 programming language system itself.
451              
452             =cut