App-OpenHAP
view release on metacpan or search on metacpan
bin/openhapd view on Meta::CPAN
#!/usr/bin/env perl
use v5.36;
use Getopt::Long;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use Fugu::Config;
use Fugu::Control;
use Protocol::HAP::Crypto;
use Fugu::Daemon;
use Fugu::Log;
use Fugu::Mdnsd;
use Fugu::MQTT;
use Fugu::Privdrop;
use Fugu::Sandbox;
use Fugu::Signal;
use App::OpenHAP::Devices;
use App::OpenHAP::Host;
use Protocol::HAP::SetupCode qw(validate_setup_code);
# Default configuration
my $config_file = '/etc/openhapd.conf';
my $foreground = 0;
my $check_config = 0;
my $verbose = 0;
GetOptions(
'c|config=s' => \$config_file,
'f|foreground' => \$foreground,
'n|check' => \$check_config,
'v|verbose+' => \$verbose,
'h|help' => \&usage,
) or usage();
# Load the configuration. A missing file is normal on a fresh
# install; a file that exists but does not parse is not, and the
# daemon must not run on defaults that the operator did not choose.
my $config = Fugu::Config->new( file => $config_file );
if ( -f $config_file && !$config->load ) {
print STDERR $config->error . "\n";
exit 1;
}
# Refuse an unusable setup code here, before the daemonize step. The
# engine requires a valid code, and it must not fail with a raw die
# after the process left the terminal. validate_setup_code also
# rejects the trivial codes that the HAP specification disallows.
my $hap_pin = $config->get( 'hap_pin', '1995-1018' );
unless ( validate_setup_code($hap_pin) ) {
print STDERR "openhapd: hap_pin \"$hap_pin\" is not a valid"
. " setup code: use 8 digits, not a trivial pattern, for"
. " example 1995-1018\n";
exit 1;
}
# Refuse an unknown log level or facility the same way, and name the
# value. openhapd.conf(5) lists exactly these spellings; a daemon
# that silently mapped an unknown one to a default would serve a
# level the operator did not choose.
my $log_level = $config->get( 'log_level', 'info' );
my $log_facility = $config->get( 'log_facility', 'daemon' );
unless ( $log_level =~ /^(?:debug|info|notice|warning|error)$/ ) {
print STDERR "openhapd: log_level \"$log_level\" is not one of"
. " debug, info, notice, warning, error\n";
exit 1;
}
unless ( $log_facility =~ /^(?:daemon|user|local[0-7])$/ ) {
print STDERR "openhapd: log_facility \"$log_facility\" is not"
. " daemon, user, or local0 through local7\n";
exit 1;
}
if ($check_config) {
print "Configuration file $config_file is valid\n";
exit 0;
}
# Daemonize first, before the log setup. This makes sure that the
# child process opens the syslog connection.
#
# The child takes /var/run/openhapd.pid while it is still root and
# holds the lock for life. The file is the trust anchor that hapctl and
# rc.d read, so it stays root-owned: the dropped daemon must not be
# able to rewrite it. Nothing removes it at exit, because an unlink in
# root-owned /var/run needs a directory permission that the daemon
# gives up. Fugu::Pidfile->is_stale covers the leftover.
if ( !$foreground ) {
Fugu::Daemon->daemonize(
logfile => '/var/log/openhapd.log',
pidfile => '/var/run/openhapd.pid',
);
}
# Set up logging after the daemonize step
my $log_mode = $foreground ? 'stderr' : 'syslog';
# Create the logger and make it the process default. Library code
# that gets no logger of its own reports through it.
my $log = Fugu::Log->new(
mode => $log_mode,
ident => 'openhapd',
level => $verbose ? 'debug' : $log_level,
facility => $log_facility,
);
Fugu::Log->set_default($log);
$log->info('OpenHAP daemon initializing');
# Get the HAP settings. hap_pin was read and validated above,
# before the -n exit and the daemonize.
my $hap_name = $config->get( 'hap_name', 'OpenHAP Bridge' );
my $hap_port = $config->get( 'hap_port', 51827 );
my $db_path = $config->get( 'db_path', '/var/db/openhapd' );
# Where hapctl asks the running daemon what it is doing. The value
# "off" turns the socket off, for an operator who wants no local
# control channel at all.
my $control_path = $config->get( 'control', '/var/run/openhapd/control.sock' );
$control_path = undef if lc $control_path eq 'off';
# A my declaration with a statement modifier has static-variable
# semantics, thus the branch is written out
my $control_dir;
$control_dir = $control_path =~ s{/[^/]+$}{}r if defined $control_path;
# Create the HAP server: the host of the Protocol::HAP engine
my $hap = App::OpenHAP::Host->new(
port => $hap_port,
pin => $hap_pin,
name => $hap_name,
storage_path => $db_path,
);
# Set up the MQTT client
my $mqtt_host = $config->get( 'mqtt_host', '127.0.0.1' );
my $mqtt_port = $config->get( 'mqtt_port', 1883 );
my $mqtt_user = $config->get('mqtt_user');
my $mqtt_pass = $config->get('mqtt_pass');
my $mqtt = Fugu::MQTT->new(
host => $mqtt_host,
port => $mqtt_port,
username => $mqtt_user,
password => $mqtt_pass,
);
# Try to connect to MQTT with a timeout
if ( $mqtt->mqtt_connect(5) ) {
$log->info( 'Connected to MQTT broker at %s:%d',
$mqtt_host, $mqtt_port );
$hap->set_mqtt_client($mqtt);
}
else {
$log->warning('MQTT broker not available, will retry in background');
$hap->set_mqtt_client($mqtt); # Set it anyway for reconnection
}
# Load the devices from the configuration
my $loader = App::OpenHAP::Devices->new();
$loader->load_devices( $config, $hap, $mqtt );
# Bump c# if the accessory database changed since the last run
$hap->update_config_number();
# Create the mDNS handle. The connect and publish steps happen after
# the privilege drop. The HAP server advertises the TXT record again
# on pairing changes.
my $mdns = Fugu::Mdnsd->new;
$hap->set_mdns($mdns);
( run in 2.320 seconds using v1.01-cache-2.11-cpan-d01c6094234 )