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 )