App-RPi-EnvUI/lib/App/RPi/EnvUI/API.pm
package App::RPi::EnvUI::API;
use strict;
use warnings;
use App::RPi::EnvUI::DB;
use App::RPi::EnvUI::Event;
use Carp qw(confess);
use Crypt::SaltedHash;
use Data::Dumper;
use DateTime;
use JSON::XS;
use Logging::Simple;
use Mock::Sub no_warnings => 1;
use RPi::Const qw(:all);
our $VERSION = '0.30';
# configure handlers
BEGIN {
if ($ENV{SUPPRESS_WARN}){
$SIG{__WARN__} = sub {};
}
}
# mocked sub handles for when we're in testing mode
# readPin(), writePin() and pinMode() from wiringPi
our ($temp_sub, $hum_sub, $rp_sub, $wp_sub, $pm_sub);
# class variables
my $api;
my $master_log;
my $log;
my $sensor;
my $events;
# class variables for the light operation
our ($light_on_at, $light_on_hours);
our ($dt_now_test, $dt_light_on, $dt_light_off);
our $light_initialized = 0;
# public environment methods
sub new {
# return the stored object if we've already run new()
if (defined $api){
$log->_5('returning stored API object');
return $api if defined $api;
}
my $self = bless {}, shift;
my $caller = (caller)[0];
$self->_args(@_, caller => $caller);
warn "API in test mode\n" if $self->testing;
$self->_init;
$api = $self;
$log->_5("successfully initialized the system");
if (! $self->testing && ! defined $events){
$self->events;
$log->_5('successfully created new async events')
}
else {
$log->_5("async events have already been spawned");
}
return $self;
}
sub action_humidity {
my ($self, $aux_id, $humidity) = @_;
my $log = $log->child('action_humidity');
$log->_6("aux: $aux_id, humidity: $humidity");
my $limit = $self->_config_control('humidity_limit');
my $min_run = $self->_config_control('humidity_aux_on_time');
$log->_6("limit: $limit, minimum runtime: $min_run");
if (! $self->aux_override($aux_id) && $humidity != -1){
if ($humidity < $limit && $self->aux_time($aux_id) == 0) {
$log->_5("humidity limit reached turning $aux_id to HIGH");
$self->aux_state($aux_id, HIGH);
$self->aux_time($aux_id, time());
}
if ($humidity >= $limit && $self->aux_time($aux_id) >= $min_run) {
$log->_5("humidity above limit setting $aux_id to LOW");
$self->aux_state($aux_id, LOW);
$self->aux_time($aux_id, 0);
}
$self->switch($aux_id);
}
}
sub action_temp {
my ($self, $aux_id, $temp) = @_;
my $log = $log->child('action_temp');
my $limit = $self->_config_control('temp_limit');
my $min_run = $self->_config_control('temp_aux_on_time');
$log->_6("limit: $limit, minimum runtime: $min_run");
if (! $self->aux_override($aux_id) && $temp != -1){
if ($temp > $limit && $self->aux_time($aux_id) == 0){
$log->_5("temp limit reached turning $aux_id to HIGH");
$self->aux_state($aux_id, HIGH);
$self->aux_time($aux_id, time);
}
elsif ($temp <= $limit && $self->aux_time($aux_id) >= $min_run){
$log->_5("temp below limit setting $aux_id to LOW");
$self->aux_state($aux_id, LOW);
$self->aux_time($aux_id, 0);
}
$self->switch($aux_id);
}
}
sub action_light {
my ($self) = @_;
#FIXME: remove set_light_times() from here and docs
# - modify/remove the light times from the db and db API (and docs)
# - figure out better method for class vars that belong in here
my $log = $log->child('action_light');
my $aux = $self->_config_control('light_aux');
my $pin = $self->aux_pin($aux);
my $override = $self->aux_override($aux);
return if $override;
my $dt_now = defined $dt_now_test
? $dt_now_test->set_time_zone('local')
: DateTime->now->set_time_zone('local');
if (! $light_initialized){
$log->_5("initializing light");
$light_on_hours = $self->_config_light('on_hours');
$light_on_at = $self->_config_light('on_at');
$log->_6("light on: $light_on_at, light hours: $light_on_hours");
($dt_light_on, $dt_light_off)
= _init_light_time($dt_now, $light_on_at, $light_on_hours);
$light_initialized = 1;
}
if ($light_on_hours == 24 || $dt_now > $dt_light_on){
if (! $self->aux_state($aux)){
$log->_6("turning light on");
$self->aux_state($aux, ON);
pin_mode($pin, OUTPUT);
write_pin($pin, HIGH);
}
}
if (! $light_on_hours || $dt_now > $dt_light_off){
if ($self->aux_state($aux)){
$log->_6("turning light off");
$self->aux_state($aux, OFF);
pin_mode($pin, OUTPUT);
write_pin($pin, LOW);
$dt_light_on = _set_light_on_time($dt_now, $light_on_at);
$dt_light_off = _set_light_off_time($dt_light_on, $light_on_hours);
}
}
}
sub aux {
my ($self, $aux_id) = @_;
my $log = $log->child('aux');
$log->_7("getting aux information for $aux_id");
return $self->db->aux($aux_id);
}
sub auxs {
my $self = shift;
my $log = $log->child('auxs');
$log->_7("retrieving all auxs");
return $self->db->auxs;
}
sub aux_id {
my ($self, $aux) = @_;
my $log = $log->child('aux_id');
$log->_7("aux ID is $aux->{id}");
return $aux->{id};
}
sub aux_override {
my $self = shift;
# sets a manual override flag if an aux is turned on manually (via button)
my ($aux_id, $override) = @_;
my $log = $log->child('aux_override');
if (! defined $aux_id || $aux_id !~ /^aux/){
confess "aux_override() requires an aux ID as its first param\n";
}
if (defined $override){
$log->_5("attempting override of aux: $aux_id");
my $toggle = $self->aux($aux_id)->{toggle};
if ($toggle != 1){
$log->_5(
"toggling of aux id $aux_id is disabled in the config file"
);
return -1;
}
$log->_5("override set operation called for $aux_id");
$self->db->update('aux', 'override', $override, 'id', $aux_id);
$log->_5("override set to $override for aux id: $aux_id");
}
return $self->aux($aux_id)->{override};
}
sub aux_pin {
my $self = shift;
# returns the auxillary's GPIO pin number
my ($aux_id, $pin) = @_;
if (! defined $aux_id || $aux_id !~ /^aux/){
confess "aux_pin() requires an aux ID as its first param\n";
}
if (defined $pin){
$self->db->update('aux', 'pin', $pin, 'id', $aux_id);
}
return $self->aux($aux_id)->{pin};
}
sub aux_state {
my $self = shift;
# maintains the auxillary state (on/off)
my ($aux_id, $state) = @_;
my $log = $log->child('aux_state');
if (! defined $aux_id || $aux_id !~ /^aux/){
confess "aux_state() requires an aux ID as its first param\n";
}
if (defined $state){
$log->_5("setting state to $state for $aux_id");
$self->db->update('aux', 'state', $state, 'id', $aux_id);
}
$state = $self->aux($aux_id)->{state};
$log->_6("$aux_id state = $state");
return $state;
}
sub aux_time {
my $self = shift;
# maintains the auxillary on time
my ($aux_id, $time) = @_;
if (! defined $aux_id || $aux_id !~ /^aux/){
confess "aux_time() requires an aux ID as its first param\n";
}
if (defined $time) {
$self->db->update('aux', 'on_time', $time, 'id', $aux_id);
}
my $on_time = $self->aux($aux_id)->{on_time};
my $on_length = time() - $on_time;
return $on_time == 0 ? 0 : $on_length;
}
sub env {
my ($self, $temp, $hum) = @_;
if (@_ != 1 && @_ != 3){
confess "env() requires either zero params, or two\n";
}
if (defined $temp){
if ($temp !~ /^\d+$/){
confess "env() temp param must be an integer\n";
}
if ($hum !~ /^\d+$/){
confess "env() humidity param must be an integer\n";
}
}
if (defined $temp){
$self->db->insert_env($temp, $hum);
}
my $event_error = 0;
if (! $self->testing){
if ($self->{events}{env_to_db}->status == -1){
$event_error = 1;
print "event failure, restarting!\n";
$self->{events}{env_to_db}->restart;
}
}
my $ret = $self->db->env;
return {temp => -1, humidity => -1, error => $event_error} if ! defined $ret;
$ret->{error} = $event_error;
return $ret;
}
sub graph_data {
my ($self) = @_;
my $graph_data = $self->db->graph_data;
my $check = 1;
my $count = 0;
my %data;
my $need = 5760 - @$graph_data; # approx 4 per min, for 24 hours (4*60*24)
for (@$graph_data) {
# we need to pad out to get to 24 hours worth of valid data
if ($need){
my $last_t = $_->[2];
my $last_h = $_->[3];
while($need){
push @{ $data{temp} }, [ $count, $last_t ];
push @{ $data{humidity} }, [ $count, $last_h ];
$need--;
$count++;
}
}
next if $_->[2] > 120;
last if $count == 300;
push @{ $data{temp} }, [ $count, $_->[2] ];
push @{ $data{humidity} }, [ $count, $_->[3] ];
$count++;
$check++;
}
return \%data;
}
sub humidity {
my $self = shift;
return $self->env->{humidity};
}
sub read_sensor {
my $self = shift;
my $log = $log->child('read_sensor');
if (! defined $self->sensor){
confess "\$self->{sensor} is not defined";
}
my $temp = $self->sensor->temp('f');
my $hum = $self->sensor->humidity;
$log->_6("temp: $temp, humidity: $hum");
return ($temp, $hum);
}
sub switch {
my ($self, $aux_id) = @_;
my $log = $log->child('switch');
my $state = $self->aux_state($aux_id);
my $pin = $self->aux_pin($aux_id);
if ($pin != -1){
if (read_pin($pin) != $state){
if ($state){
$log->_6("set $pin state to HIGH");
pin_mode($pin, OUTPUT);
write_pin($pin, HIGH);
}
else {
$log->_6("set $pin state to LOW");
pin_mode($pin, OUTPUT);
write_pin($pin, LOW);
}
}
else {
$log->_6("pin $pin state already set properly");
}
}
}
sub temp {
my $self = shift;
return $self->env->{temp};
}
# public core operational methods
sub auth {
my ($self, $user, $pw) = @_;
if (! defined $user){
confess "\n\nauth() requires a username sent in\n\n";
}
if (! defined $pw){
confess "\n\nauth() requires a password sent in\n\n";
}
my $csh = Crypt::SaltedHash->new(algorithm => 'SHA1');
my $crypted = $self->db->user($user)->{pass};
return $csh->validate($crypted, $pw);
}
sub events {
my $self = shift;
my $log = $log->child('events');
$events = App::RPi::EnvUI::Event->new($self->testing);
$self->{events}{env_to_db} = $events->env_to_db;
$self->{events}{env_action} = $events->env_action;
$self->{events}{env_to_db}->start;
$self->{events}{env_action}->start;
$log->_5("events successfully started");
}
sub log {
my $self = shift;
$master_log->file($self->log_file) if $self->log_file;
$master_log->level($self->debug_level);
return $master_log;
}
sub passwd {
my ($self, $pw) = @_;
if (! defined $pw){
confess "\n\nplain text password string required\n\n";
}
my $csh = Crypt::SaltedHash->new(
algorithm => 'SHA1',
);
$csh->add($pw);
my $salted = $csh->generate;
return $salted;
}
sub user {
my ($self, $un) = @_;
if (! defined $un){
confess "\n\nuser() requires a username to be sent in\n\n";
}
return $self->db->user($un);
}
# public configuration getters
sub env_humidity_aux {
return $_[0]->_config_control('humidity_aux');
}
sub env_light_aux {
return $_[0]->_config_control('light_aux');
}
sub env_temp_aux {
return $_[0]->_config_control('temp_aux');
}
# public instance variable methods
sub config {
$_[0]->{config_file} = $_[1] if defined $_[1];
return $_[0]->{config_file} || 'config/envui.json';
}
sub db {
my ($self, $db) = @_;
$self->{db} = $db if defined $db;
return $self->{db};
}
sub debug_sensor {
my ($self, $bool) = @_;
if (defined $bool){
$self->{debug_sensor} = $bool;
}
return $self->{debug_sensor};
}
sub log_file {
my ($self, $fn) = @_;
if (defined $fn){
$self->{log_file} = $fn;
}
return $self->{log_file};
}
sub debug_level {
my ($self, $level) = @_;
if (defined $level){
if ($level < -1 || $level > 7){
warn "log level has to be between 0 and 7... disabling logging\n";
$level = -1;
}
$self->{debug_level} = $level;
}
return $self->{debug_level};
}
sub sensor {
my ($self, $sensor) = @_;
$self->{sensor} = $sensor if defined $sensor;
return $self->{sensor};
}
sub testing {
my ($self, $bool) = @_;
if (defined $bool){
$self->{testing} = $bool;
}
$self->{testing} = 1 if _ui_test_mode();
return $self->{testing};
}
sub test_mock {
my ($self, $mock) = @_;
if (defined $mock){
$self->{test_mock} = $mock;
}
$self->{test_mock} = 1 if ! defined $self->{test_mock};
return $self->{test_mock};
}
# private
sub _args {
my ($self, %args) = @_;
$self->debug_sensor($args{debug_sensor});
$self->config($args{config_file});
$self->log_file($args{log_file});
$self->debug_level($args{debug_level});
$self->testing($args{testing});
$self->test_mock($args{test_mock});
}
sub _bool {
# translates javascript true/false and 1/0 to 1/0
my ($self, $bool) = @_;
if (! defined $bool){
confess
"\$bool param must be present and must be 'true', 'false', 1 or 0";
}
return $bool ? 1 : 0 if $bool =~ /\d/;
return 1 if $bool eq 'true';
return 0 if $bool eq 'false';
confess "\$bool param must be either true/false or 1/0";
}
sub _config_control {
my ($self, $want) = @_;
return $self->db->config_control($want);
}
sub _config_core {
my ($self, $want) = @_;
if (! defined $self->db){
confess "API's DB object is not defined.";
}
if (! defined $want){
confess "_config_core() requires a \$want param\n";
}
return $self->db->config_core($want);
}
sub _config_light {
my ($self, $want) = @_;
my %conf;
my $light = $self->db->config_light;
for (keys %$light){
if ($_ eq 'on_hours'){
my $on_hrs = $light->{on_hours}->{value};
if ($on_hrs !~ /^\d+$/ || $on_hrs < 0 || $on_hrs > 24){
confess "\n\non_hours config file directive must be between ".
"0 and 24\n\n"
}
}
$conf{$_} = $light->{$_}{value};
}
if (defined $want){
return $conf{$want};
}
return \%conf;
}
sub _init {
my ($self) = @_;
$self->db(
App::RPi::EnvUI::DB->new(
testing => $self->testing
)
);
$self->debug_level($self->_config_core('debug_level'));
$self->log_file($self->_config_core('log_file'));
$self->_log;
my $log = $log->child('_init()');
if ($self->testing){
$log->_5('in test mode');
$self->_test_mode
}
else {
$log->_5('in prod mode');
$self->_prod_mode;
}
}
sub _init_light_time {
my ($dt_now, $on_at, $on_hours) = @_;
$dt_light_on = _set_light_on_time($dt_now, $on_at);
$dt_light_off = _set_light_off_time($dt_light_on, $on_hours);
return ($dt_light_on, $dt_light_off);
}
sub _test_mode {
my ($self) = @_;
my $log = $log->child('_test_mode');
$log->_6("testing mode");
$self->testing(1);
$self->db(App::RPi::EnvUI::DB->new(testing => 1));
$self->config('t/envui.json');
$self->_parse_config;
if ($self->test_mock) {
my $mock = Mock::Sub->new;
$temp_sub = $mock->mock(
'RPi::DHT11::temp',
return_value => 80
);
$log->_6( "mocked RPi::DHT11::temp" );
$hum_sub = $mock->mock(
'RPi::DHT11::humidity',
return_value => 20
);
$log->_6( "mocked RPi::DHT11::humidity" );
$pm_sub = $mock->mock(
'App::RPi::EnvUI::API::pin_mode',
return_value => 'ok'
);
$rp_sub = $mock->mock(
'App::RPi::EnvUI::API::read_pin',
);
$wp_sub = $mock->mock(
'App::RPi::EnvUI::API::write_pin',
return_value => 'ok'
);
}
$log->_5(
"mocked WiringPi::write_pin as App::RPi::EnvUI::API::write_pin"
);
warn "API in test mode\n";
$self->sensor(bless {}, 'RPi::DHT11');
$log->_5("blessed a fake sensor");
}
sub _prod_mode {
my ($self) = @_;
my $log = $log->child('_prod_mode');
$self->_parse_config;
if (! exists $INC{'WiringPi/API.pm'} && ! $self->testing){
require WiringPi::API;
WiringPi::API->import(qw(:perl));
}
if (! exists $INC{'RPi/DHT11.pm'} && ! $self->testing){
require RPi::DHT11;
RPi::DHT11->import;
}
$log->_6("required/imported WiringPi::API and RPi::DHT11");
if (! defined $sensor){
$sensor = RPi::DHT11->new(
$self->_config_core('sensor_pin'), $self->debug_sensor
);
}
$self->sensor($sensor);
$log->_6("instantiated a new RPi::DHT11 sensor object");
}
sub _log {
my ($self) = @_;
# configures the class-level log
$master_log = Logging::Simple->new(
name => 'EnvUI',
print => 1,
file => $self->log_file,
level => $self->debug_level
);
$log = $master_log->child('API');
}
sub _parse_config {
my ($self, $config) = @_;
$self->db->begin;
$self->_reset;
$config = $self->config if ! defined $config;
if (! -e $config){
confess "\n\nconfig file '$config' not found...\n\n";
}
my $json;
{
local $/;
open my $fh, '<', $config or confess $!;
$json = <$fh>;
}
my $conf = decode_json $json;
# auxillary channels
{ # pin numbers
my $db_struct = [
'aux',
'pin',
'id',
];
my @data;
for (1 .. 8) {
my $aux_id = "aux$_";
my $pin = $conf->{$aux_id}{pin};
push @data, [$pin, $aux_id];
}
$self->db->update_bulk(@$db_struct, \@data);
}
{ # aux toggle
my $db_struct = [
'aux',
'toggle',
'id',
];
my @data;
for (1 .. 8) {
my $aux_id = "aux$_";
my $toggle = $conf->{$aux_id}{toggle};
push @data, [$toggle, $aux_id];
}
$self->db->update_bulk(@$db_struct, \@data);
}
for my $conf_section (qw(control core light)){
my $db_struct = [
$conf_section,
'value',
'id'
];
my @data;
for my $directive (keys %{ $conf->{$conf_section} }){
push @data, [
$conf->{$conf_section}{$directive},
$directive
];
# populate some internal variables from the 'core'
# config section
if ($conf_section eq 'core'){
next if $directive eq 'testing';
$self->{$directive} = $conf->{$conf_section}{$directive};
}
}
$self->db->update_bulk(@$db_struct, \@data);
}
$self->db->commit;
}
sub _reset {
my $self = shift;
# reset dynamic db attributes
my $log = $log->child('_reset');
$log->_5("reset() called");
$self->db->update_bulk_all(
'aux', 'state', [0]
);
$self->db->update_bulk_all(
'aux', 'override', [0]
);
$self->db->update_bulk_all(
'aux', 'on_time', [0]
);
# remove all statistics
$self->db->delete('stats');
}
sub _set_light_off_time {
my ($dt_on, $on_time) = @_;
my $dt_off = $dt_on->clone;
$dt_off->add(hours => $on_time);
return $dt_off;
}
sub _set_light_on_time {
my ($dt_now, $on_at) = @_;
my $dt_on = $dt_now->clone;
$dt_on->set_second(0);
$dt_on->set_hour((split(/:/, $on_at))[0]);
$dt_on->set_minute((split(/:/, $on_at))[1]);
if ($dt_on < $dt_now){
# this situation happens if the light on and off times are within the
# same 24 hour period. We check to see if the updated on time is less
# than now; if it is, we need to advance to tomorrow
$dt_on->add(hours => 24);
}
return $dt_on;
}
sub _ui_test_mode {
return -e 't/testing.lck';
}
1;
__END__
=head1 NAME
App::RPi::EnvUI::API - Core API abstraction class for the
App::RPi::EnvUI web app
=head1 SYNOPSIS
my $api = App::RPi::EnvUI::API->new;
... #FIXME: add a real example
=head1 DESCRIPTION
This class can be used outside of the L<App::RPi::EnvUI> web application to
update settings, read statuses, perform analysis and generate reports.
It's primary purpose is to act as an intermediary between the web app itself,
the asynchronous events that run within their own processes, the environment
sensors, and the application database.
=head1 METHODS
=head2 new(%args)
Instantiates a new core API object. Send any/all parameters in within hash
format (eg: C< testing =\> 1)).
Parameters:
config
Optional, String. Name of the configuration file to use. Very rarely required.
Default: C<config/envui.json>
testing
Optional, Bool. Send in C<1> to enable testing, C<0> to disable it.
Default: C<0>
test_mock
This flag is only useful when C<testing> param is set to true, and should only
be used when writing unit tests for the L<App::RPi::EnvUI::Event> class. Due to
the way the system works, the API has to avoid mocking out items in test mode,
and the mocks have to be set within the test file itself. Do not use this flag
unless you are writing unit tests.
debug_level
Optional, Integer. Send in a level of C<0-7> to enable logging.
Default: C<-1> (logging disabled)
log_file
Optional, String. Name of file to log to. We log to C<STDOUT> by default. The
C<debug_level> parameter must be changed from default for this parameter to have
any effect.
Default: C<undef>
debug_sensor
Optional, Bool. Enable/disable debug print output from the L<RPi::DHT11> sensor
code. Send in C<1> to enable, and C<0> to disable.
Default: C<0> (off)
=head2 action_humidity($aux_id, $humidity)
Performs the check of the current humidity against the configured set limit, and
enables/disables any devices attached to the humidity auxillary GPIO pin, if
set.
Parameters:
$aux_id
Mandatory, String. The string name representation of the humidity auxillary. By
default, this will be C<aux2>.
$humidity
Mandatory: Integer. The integer value of the current humidity (typically
supplied by the C<RPi::DHT11> hygrometer sensor.
=head2 action_light($dt)
Performs the time calculations on the configured light on/off event settings,
and turns the GPIO pin associated with the light auxillary channel on and off as
required.
Parameters (only used for testing):
%args
Optional (use for testing only!). Pass in a hash with the desired configuration
parameters as found in the configuration file for light configuration.
=head2 action_temp($aux_id, $temperature)
Performs the check of the current temperature against the configured set limit,
and enables/disables any devices attached to the temp auxillary GPIO pin, if
set.
Parameters:
$aux_id
Mandatory, String. The string name representation of the temperature auxillary.
By default, this will be C<aux1>.
=head2 auth($user, $pw)
Checks whether a user is supplying the correct password.
Parameters:
$user
Mandatory, String. The user name to validate the password for.
$pw
Mandatory, String. The plain text password to verify.
Return: True (C<1>) if successful, C<undef> otherwise.
=head2 aux($aux_id)
Retrieves from the database a hash reference that contains the details of a
specific auxillary channel, and returns it.
Parameters:
$aux_id
Mandatory, String. The string name representation of the auxillary channel to
retrieve (eg: C<aux1>).
Returns: Hash reference with the auxillary channel details.
=head2 auxs
Fetches the details of all the auxillary channels from the database. Takes no
parameters.
Return: A hash reference of hash references, where each auxillary channel name
is a key, and the value is a hash reference containing that auxillary channel's
details.
=head2 aux_id($aux)
Extracts the name/ID of a specific auxillary channel.
Parameters:
$aux
Mandatory, href. A hash reference as returned from a call to C<aux()>.
Return: String. The name/ID of the specified auxillary channel.
=head2 aux_override($aux_id, $override)
Sets/gets the override status of a specific aux channel.
The override functionality is a flag in the database that informs the system
that automated triggering of an auxillary GPIO pin should be bypassed due to
user override.
Parameters:
$aux_id
Mandatory, String. The string name of an auxillary channel (eg: C<aux1>).
$state
Optional, Bool. C<0> to disable an aux pin override, C<1> to enable it.
Return: Bool. Returns the current status of the aux channel's override flag.
=head2 aux_pin($aux_id, $pin)
Associates a GPIO pin to a specific auxillary channel.
Parameters:
$aux_id
Mandatory, String. The string name of an auxillary channel (eg: C<aux1>).
$pin
Optional, Integer. The GPIO pin number that you want associated with the
specified auxillary channel.
Return: The GPIO pin number associated with the auxillary channel specified.
=head2 aux_state($aux_id, $state)
Sets/gets the state (ie. on/off) value of a specific auxillary channel's GPIO
pin.
Parameters:
$aux_id
Mandatory, String. The string name of an auxillary channel (eg: C<aux1>).
$state
Optional, Bool. C<0> to turn the pin off (C<LOW>), or C<1> to turn it on
(C<HIGH>).
Return: Bool. Returns the current state of the aux pin.
=head2 aux_time($aux_id, $time)
Sets/gets the length of time an auxillary channel's GPIO pin has been C<HIGH>
(on). Mainly used to determine timers.
Parameters:
$aux_id
Mandatory, String. The string name of an auxillary channel (eg: C<aux1>).
$time
Optional, output from C<time()>. If sent in, we'll set the start time of a pin
on event to this.
Return, Integer (seconds). Returns the elapsed time in seconds since the last
timestamp was sent in with the C<$time> parameter, after being subtracted with
a current C<time()> call. If C<$time> has not been sent in, or an internal timer
has reset this value, the return will be zero (C<0>).
=head2 config($conf_file)
Sets/gets the currently loaded configuration file.
Parameters:
$conf_file
Optional, String. The name of a configuration file. This is only useful on
instantiation of a new object.
Default: C<config/envui.json>
Returns the currently loaded configuration file name.
=head2 db($db_object)
Sets/gets the internal L<App::RPi::EnvUI::DB> object. This method allows you to
swap DB objects (and thereby DB handles) within separate processes.
Parameters:
$db_object
Optional, L<App::RPi::EnvUI::DB> object instance.
Returns: The currently loaded DB object instance.
=head2 debug_sensor($bool)
Enable/disable L<RPi::DHT11> sensor's debug print output.
Parameters:
$bool
Optional, Bool. C<1> to enable debugging, C<0> to disable.
Return: Bool. The current state of the sensor's debug state.
Default: False (C<0>)
=head2 env($temp, $humidity)
Sets/gets the current temperature and humidity pair.
Parameters:
All parameters are optional, but if one is sent in, both must be sent in.
$temp
Optional, Integer. The current temperature.
$humidity
Optional, Integer. The current humidity .
Return: A hash reference in the format C<{temp => Int, humidity => Int}>
=head2 env_humidity_aux
Returns the string name of the humidity auxillary channel (default: C<aux2>).
Takes no parameters.
=head2 env_temp_aux
Returns the string name of the temperature auxillary channel (default: C<aux1>).
Takes no parameters.
=head2 env_light_aux
Returns the string name of the light auxillary channel (default: C<aux3>).
Takes no parameters.
=head2 events
Initializes and starts the asynchronous timed events that operate in their own
processes, performing actions outside of the main thread.
Takes no parameters, has no return.
=head2 graph_data
Returns a hash reference with keys C<temp> and C<humidity>, where the values of
each are an array reference of array references with the inner arefs containing
the element number and the temp/humidity value.
It attempts to fetch data for 24 hours, sampling approximately every minute. If
no data is found far enough back, the temp/humidity will be set to C<0>.
=head2 humidity
Returns as an integer, the current humidity level.
=head2 temp
Returns as an integer, the current temperature level.
=head2 set_light_time
Sets in the database the values for lights-on and lights-off time.
Takes no parameters, there is no return.
=head2 log
Returns a pre-configured L<Logging::Simple> object, ready to be cloned with its
C<child()> method.
=head2 log_file($filename)
Sets/gets the log file for the internal logger.
Parameters:
$filename
Optional, String. The name of the log file to use. Note that this won't have any
effect when used in user space, and is mainly a convenience method. It's used
when instantiating a new object.
Return: The string name of the currently in-use log file, if set.
=head2 debug_level($level)
Sets/gets the current debug logging level.
Parameters:
$level
Optional, Integer. Sets the logging level between C<0-7>.
Return: Integer, the current level.
Default: C<-1> (logging disabled)
=head2 passwd($pw)
Generates an SHA-1 hashed password.
Parameters:
$pw
Mandatory, String. A plain text string (the password).
Return: SHA-1 hashed password string.
=head2 read_sensor
Retrieves and returns the current temperature and humidity within an array of
two integers.
=head2 sensor($sensor)
Sets/gets the current hygrometer sensor object. This method is here so that for
testing, we can send in mocked sensor objects.
Parameters:
$sensor
Optional, L<RPi::DHT11> object instance.
Return: The sensor object.
=head2 set_light_times
Internal method that sets the light on-off times in the database, once on webapp
initial startup, then at the end of the lights-off trigger in C<action_light()>.
=head2 switch($aux_id)
Enables/disables the GPIO pin associated with the specified auxillary channel,
based on what the current state of the pin is. If it's currently off, it'll be
turned on, and vice-versa.
Parameters:
$aux_id
Mandatory, String. The string name of the auxillary channel to have it's GPIO
pin switched (eg: C<aux1>).
Return: none
=head2 testing($bool)
Used primarily internally, sets/gets whether we're in testing mode or not.
Parameters:
$bool
Optional, Bool. C<0> for production mode, and C<1> for testing mode.
Return: Bool, whether we're in testing mode or not.
=head2 test_mock($bool)
This is for use internally when testing the L<App::RPi::EnvUI::Event> module.
Normally, the API mocks out items for testing, but in C<Event>'s case, the test
file itself has to do the mocking.
Parameters:
$bool
Optional, Bool. C<0> to disable, C<1> to enable.
Default: C<1>, enabled (the API will mock in test mode)
=head2 user($user)
Fetches a user's details.
Parameters:
$user
Mandatory, String. The username of the user to fetch details for.
Return: href, the hash reference containing user details per the 'user' database
table.
=head1 ENVIRONMENT VARIABLES
=head2 SUPPRESS_WARN
Set this variable to a true value to suppress all warnings via a
C<<$SIG{__WARN__}>> handler. This is handy when running tests, when you don't
need to know specific details about core workings.
Implemented in C<<API.pm>>.
=head1 AUTHOR
Steve Bertrand, E<lt>steveb@cpan.org<gt>
=head1 LICENSE AND COPYRIGHT
Copyright 2017 Steve Bertrand.
This program is free software; you can redistribute it and/or modify it
under the terms of either: the GNU General Public License as published
by the Free Software Foundation; or the Artistic License.
See L<http://dev.perl.org/licenses/> for more information.