App-EventStreamr/bin/station-mgr.pl
#!/usr/bin/perl
use strict;
use v5.10;
use FindBin qw($Bin);
use lib "$Bin/../lib";
use Proc::Daemon; # libproc-daemon-perl
use JSON; # libjson-perl
use Config::JSON; # libconfig-json-perl
use HTTP::Tiny; # libhttp-tiny-perl
use Log::Log4perl; # liblog-log4perl-perl
use POSIX;
use File::Path qw(make_path);
use File::Basename;
use experimental 'switch';
use Getopt::Long;
use Data::Dumper;
# PODNAME: station-mgr
# ABSTRACT: station-mgr - Core Station Manager script
our $VERSION = '0.5'; # VERSION
my $DEBUG = 0;
my $DAEMON = 1;
my $getopts_rc = GetOptions(
"debug!" => \$DEBUG,
"daemon!" => \$DAEMON,
"help|?" => \&print_usage,
);
# setup signal handlers and daemon stuff
# POSIX unmasks the sigprocmask properly
$SIG{CHLD} = 'IGNORE';
my $sigset = POSIX::SigSet->new();
my $update = POSIX::SigAction->new( 'self_update',
$sigset,
&POSIX::SA_NODEFER);
my $exit = POSIX::SigAction->new( 'sig_exit',
$sigset,
&POSIX::SA_NODEFER);
my $pipe = POSIX::SigAction->new( 'sig_pipe',
$sigset,
&POSIX::SA_NODEFER);
my $get = POSIX::SigAction->new( 'get_config',
$sigset,
&POSIX::SA_NODEFER);
my $post = POSIX::SigAction->new( 'post_config',
$sigset,
&POSIX::SA_NODEFER);
# Handle INT/Term
POSIX::sigaction(&POSIX::SIGTERM, $exit);
POSIX::sigaction(&POSIX::SIGINT, $exit);
# SIGHUP updates and execs
POSIX::sigaction(&POSIX::SIGHUP, $update);
POSIX::sigaction(&POSIX::SIGPIPE, $pipe);
# USR1/2 for get/post config
POSIX::sigaction(&POSIX::SIGUSR1, $get);
POSIX::sigaction(&POSIX::SIGUSR2, $post);
our $daemon = Proc::Daemon->new(
work_dir => "$Bin/../",
);
our $daemons;
if ( $DAEMON ) {
$daemon->Init();
}
# set umask
umask 0027;
# EventStremr Modules
use App::EventStreamr::Devices;
our $devices = App::EventStreamr::Devices->new();
use App::EventStreamr::Utils;
our $utils = App::EventStreamr::Utils->new();
# Load/Build Local Config
my $localconfig;
if (-e "$Bin/../settings.json") {
$localconfig = Config::JSON->new("$Bin/../settings.json");
$localconfig = $localconfig->{config};
} else {
$localconfig = Config::JSON->create("$Bin/../settings.json");
$localconfig->{config} = blank_settings();
$localconfig->write;
$localconfig = $localconfig->{config};
}
# Station Config
our $stationconfig;
if (-e "$Bin/../station.json") {
$stationconfig = Config::JSON->new("$Bin/../station.json");
} else {
$stationconfig = Config::JSON->create("$Bin/../station.json");
$stationconfig->{config} = blank_station();
$stationconfig->write;
}
# Commands
my $commands = Config::JSON->new("$Bin/../commands.json");
# Own data
our $self;
$self->{config} = $stationconfig->{config};
$self->{config}{macaddress} = getmac();
$self->{devices} = $devices->all();
$self->{commands} = $commands->{config};
$self->{dvswitch}{check} = 1; # check until dvswitch is found
$self->{dvswitch}{running} = 0;
$self->{settings} = $localconfig;
$self->{date} = strftime "%Y%m%d", localtime;
if ($self->{config}{run} == 2) {$self->{config}{run} = 1;}
# Logging
unless ( $DEBUG ) {
$self->{loglevel} = 'INFO, LOG1' ;
} else {
$self->{loglevel} = 'DEBUG, LOG1, SCREEN' ;
}
unless (-d "$Bin/../logs/") {
make_path("$Bin/../logs/");
}
my $log_conf = qq(
log4perl.rootLogger = $self->{loglevel}
log4perl.appender.SCREEN = Log::Log4perl::Appender::Screen
log4perl.appender.SCREEN.stderr = 0
log4perl.appender.SCREEN.layout = Log::Log4perl::Layout::PatternLayout
log4perl.appender.SCREEN.layout.ConversionPattern = %m %n
log4perl.appender.LOG1 = Log::Log4perl::Appender::File
log4perl.appender.LOG1.utf8 = 1
log4perl.appender.LOG1.filename = $Bin/../logs/station-mgr.log
log4perl.appender.LOG1.mode = append
log4perl.appender.LOG1.layout = Log::Log4perl::Layout::PatternLayout
log4perl.appender.LOG1.layout.ConversionPattern = %d %p %m %n
);
Log::Log4perl::init(\$log_conf);
our $logger = Log::Log4perl->get_logger();
$logger->info("manager starting: pid=$$, station_id=$self->{config}->{macaddress}");
$daemons->{main}{run} = 1;
# HTTP
our $http = HTTP::Tiny->new(timeout => 15);
# Start the API
api();
# Register with controller
$logger->info("Registering with controller $localconfig->{controller}/api/station/$self->{config}{macaddress}");
my $response = $http->post("$localconfig->{controller}/api/station/$self->{config}{macaddress}");
# Controller responds with created 201, post our config
if ($response->{status} == 201) {
$logger->info("Posting config $localconfig->{controller}");
# Status Post Data
my $json = to_json($self->{config});
my %headers = (
'station-mgr' => 1,
'Content-Type' => 'application/json',
);
my %post_data = (
content => $json,
headers => \%headers,
);
$response = $http->post("$localconfig->{controller}/api/station", \%post_data);
$logger->debug({filter => \&Data::Dumper::Dumper,
value => $response}) if ($logger->is_debug());
}
if ($response->{status} == 200 ) {
my $content = from_json($response->{content});
$self->{controller}{running} = 1;
$logger->debug({filter => \&Data::Dumper::Dumper,
value => $content}) if ($logger->is_debug());
if (defined $content && $content ne 'true') {
$self->{config} = $content->{settings};
write_config();
} else {
write_config();
}
# Run all connected devices - need to get devices to return an array
if ($self->{config}{devices} eq 'all') {
$self->{config}{devices} = $self->{devices}{array};
}
$self->{config}{manager}{pid} = $$;
post_config();
} elsif ($response->{status} == 204){
$self->{controller}{running} = 1;
$self->{config}{manager}{pid} = $$;
$logger->warn("Connected but not registered");
$logger->info("Falling back to local config");
$logger->debug({filter => \&Data::Dumper::Dumper,
value => $response}) if ($logger->is_debug());
# Run all connected devices - need to get devices to return an array
if ($self->{config}{devices} eq 'all') {
$self->{config}{devices} = $self->{devices}{array};
}
post_config();
} else {
chomp $response->{content};
$self->{controller}{running} = 0;
$self->{config}{manager}{pid} = $$;
$logger->warn("Failed to connect: $response->{content}");
$logger->info("Falling back to local config");
$logger->debug({filter => \&Data::Dumper::Dumper,
value => $response}) if ($logger->is_debug());
# Run all connected devices - need to get devices to return an array
if ($self->{config}{devices} eq 'all') {
$self->{config}{devices} = $self->{devices}{array};
}
post_config();
}
# Debug logging of data
$logger->debug({filter => \&Data::Dumper::Dumper,
value => $self}) if ($logger->is_debug());
# Log when started
if ($self->{config}{run}) {
$logger->info("Manager started, starting devices");
} else {
$logger->info("Manager started, configuration set to not start devices.");
}
# Post start clearing of data
$self->{device_control}{record}{recordpath} = 0;
# Main Daemon Loop
while ($daemons->{main}{run}) {
# If we're restarting, we should trigger check for dvswitch
if ($self->{config}{run} == 2) {
$logger->info("Restart Trigged");
$self->{dvswitch}{check} = 1;
}
# Process the internal commands
api();
devmon();
# Process the roles
foreach my $role (@{$self->{config}->{roles}}) {
given ( $role ) {
when ("mixer") { mixer(); }
when ("ingest") { ingest(); }
when ("stream") { stream(); }
when ("record") { record(); }
}
}
# 2 is the restart all processes trigger
# $daemon->Kill_Daemon does a Kill -9, so if we get here they procs should be dead.
if ($self->{config}{run} == 2) {
$self->{config}{run} = 1;
}
# Until found check for dvswitch - continuously hitting dvswitch with an unknown client caused high cpu load
unless ( $self->{dvswitch}{running} && ! $self->{dvswitch}{check} ) {
if ( $utils->port($self->{config}->{mixer}{host},$self->{config}->{mixer}{port}) ) {
$logger->info("DVswitch found Running");
$self->{dvswitch}{running} = 1;
$self->{dvswitch}{check} = 0; # We can set this to 1 and it will check dvswitch again.
}
}
# Uncomment to enable heartbeat
## Post a hearbeat to the controller/mixer
#if ((time % 10) == 0) {
# $logger->debug("Heartbeat!") if ($logger->is_debug());
# $self->{heartbeat} = time;
# post_config();
#}
# Update date if it's changed - I wonder if there is a better way to trigger this? Cron (requires more OS config)?
unless ( $self->{date} == strftime "%Y%m%d", localtime) {
$self->{date} = strftime "%Y%m%d", localtime;
$self->{device_control}{record}{recordpath} = 0;
$self->{config}{device_control}{record}{run} = 2;
$self->{config}{device_control}{sync}{run} = 2;
}
sleep 1;
}
# ---- SUBROUTINES ----------------------------------------------------------
sub sig_exit {
$logger->info("manager exiting...");
$daemons->{main}{run} = 0;
$daemon->Kill_Daemon($self->{device_control}{api}{pid});
$daemon->Kill_Daemon($self->{device_control}{devmon}{pid});
$daemon->Kill_Daemon($self->{device_control}{sync}{pid});
}
sub sig_pipe {
$logger->debug( "caught SIGPIPE" ) if ( $logger->is_debug() );
}
sub self_update {
$logger->info("Performing self update");
$logger->debug("Update host: $Bin/../../baseimage/update-host.sh") if ($logger->is_debug());
system("$Bin/../../baseimage/update-host.sh");
sig_exit();
my $options;
$options = "--debug" if $DEBUG;
$options = "$options --no-daemon" unless $DAEMON;
my $script = File::Basename::basename($0);
$logger->debug("Restart Manger: $Bin/$script $options") if ($logger->is_debug());
exec("$Bin/$script $options") or $logger->logdie("Couldn't restart: $!");
}
sub print_usage {
say "
Usage: station-mgr.pl [OPTIONS]
Options:
--no-deaemon disable daemon
--debug turn on debugging
--help this help text
";
exit 0;
}
# Config triggers
sub post_config {
# Refresh devices
$self->{devices} = $devices->all();
# Post to manager api
my $json = to_json($self);
my %post_data = (
content => $json,
'content-type' => 'application/json',
'content-length' => length($json),
);
my $post = $http->post("http://127.0.0.1:3000/internal/settings", \%post_data);
$logger->info("Config Posted to API");
$logger->debug({filter => \&Data::Dumper::Dumper,
value => $post}) if ($logger->is_debug());
# Status information
my $status;
$status->{status} = $self->{status};
$status->{macaddress} = $self->{config}{macaddress};
$status->{nickname} = $self->{config}{nickname};
# Uncomment for heartbeat
#$status->{heartbeat} = $self->{heartbeat};
# Post Headers
my %headers = (
'station-mgr' => 1,
'Content-Type' => 'application/json',
);
$logger->debug({filter => \&Data::Dumper::Dumper,
value => $status}) if ($logger->is_debug());
# Status Post Data
$json = to_json($status);
%post_data = (
content => $json,
headers => \%headers,
);
# Post Status to Mixer
$post = $http->post("http://$self->{config}{mixer}{host}:3000/status/$self->{config}{macaddress}", \%post_data);
$logger->info("Status Posted to Mixer API -> http://$self->{config}{mixer}{host}:3000/status/$self->{config}{macaddress}");
$logger->debug({filter => \&Data::Dumper::Dumper,
value => $post}) if ($logger->is_debug());
# Post Status + devices to Controller
if ($self->{controller}{running}) {
# Build post object
my $data;
$data->[0]{key} = "status";
$data->[0]{value} = $status->{status};
$data->[1]{key} = "devices";
$data->[1]{value} = $self->{devices}{all};
delete $data->[1]{value}{all};
# Post data
$json = to_json($data);
# some bug and it's late this could cause hideous issues if
# a device id has a / in it, but this should be unlikely
$json =~ s{/|\.}{}g;
%post_data = (
content => $json,
headers => \%headers,
);
$logger->debug({filter => \&Data::Dumper::Dumper,
value => $json}) if ($logger->is_debug());
$post = $http->post("$localconfig->{controller}/api/stations/$self->{config}{macaddress}/partial", \%post_data);
$logger->info("Status Posted to Controller API - $localconfig->{controller}/api/stations/$self->{config}{macaddress}/partial");
$logger->debug({filter => \&Data::Dumper::Dumper,
value => $post}) if ($logger->is_debug());
}
return;
}
sub get_config {
my $get = $http->get("http://127.0.0.1:3000/internal/settings");
my $content = from_json($get->{content});
$self->{config} = $content->{config};
$logger->debug({filter => \&Data::Dumper::Dumper,
value => $get}) if ($logger->is_debug());
$logger->debug({filter => \&Data::Dumper::Dumper,
value => $self}) if ($logger->is_debug());
$logger->info("Config recieved from API");
write_config();
return;
}
sub write_config {
$stationconfig->{config} = $self->{config};
$stationconfig->write;
$logger->info("Config written to disk");
return;
}
## api
sub api {
my $device;
unless ($logger->is_debug()) {
$self->{device_commands}{api}{command} = "/usr/bin/plackup -s Twiggy -p 3000 $Bin/station-api.pl --daemon --environment production";
} else{
$self->{device_commands}{api}{command} = "/usr/bin/plackup -s Twiggy -p 3000 $Bin/station-api.pl";
}
$device->{role} = "api";
$device->{id} = "api";
$device->{type} = "internal";
run_stop($device);
return;
}
## devmon
sub devmon {
my $device;
$self->{device_commands}{devmon}{command} = "$Bin/station-devmon.pl";
$device->{role} = "devmon";
$device->{id} = "devmon";
$device->{type} = "internal";
run_stop($device);
return;
}
## Ingest
sub ingest {
if ($self->{dvswitch}{running} == 1) {
foreach my $device (@{$self->{config}{devices}}) {
# Set Role
$device->{role} = "ingest";
if ($device->{type} eq "dv") {
# Check dv exists
if (-e $self->{devices}{dv}{$device->{id}}{path}) {
run_stop($device);
# If we're restarting we should refresh the devices and try again
} elsif ($self->{config}{device_control}{$device->{id}}{run} == 1) {
$logger->warn("$device->{id} has been disconnected");
# It's not ideal, but dvgrab hangs if no camera exist. devmon will restart it when it's plugged in again.
$self->{config}{device_control}{$device->{id}}{run} = 0;
run_stop($device);
# Set status
$self->{device_control}{$device->{id}}{timestamp} = time;
$self->{status}{$device->{id}}{running} = 0;
$self->{status}{$device->{id}}{status} = "disconnected";
$self->{status}{$device->{id}}{state} = "hard";
post_config();
} elsif ($self->{config}{device_control}{$device->{id}}{run} == 2) {
$logger->warn("$device->{id} has been restarted, refreshing devices");
$self->{devices} = $devices->all();
post_config();
run_stop($device);
}
} else {
run_stop($device);
}
}
}
return;
}
## Mixer
sub mixer {
my $device;
$device->{role} = "mixer";
$device->{id} = "dvswitch";
$device->{type} = "mixer";
run_stop($device);
my $loop;
if ($self->{config}{mixer}{loop} && $self->{dvswitch}{running}) {
if (-e $self->{config}{mixer}{loop}) {
$loop->{role} = "ingest";
$loop->{id} = $self->{config}{mixer}{loop};
$loop->{type} = "file";
run_stop($loop);
} else {
# Set status
$self->{device_control}{$loop->{id}}{timestamp} = time;
$self->{status}{$loop->{id}}{running} = 0;
$self->{status}{$loop->{id}}{status} = "file_not_found";
$self->{status}{$loop->{id}}{state} = "hard";
$self->{status}{$loop->{id}}{name} = "standby loop";
post_config();
}
}
return;
}
## Stream
sub stream {
my $device;
$device->{role} = "stream";
$device->{id} = $self->{config}{stream}{stream};
$device->{type} = "stream";
run_stop($device);
return;
}
## Record
sub record {
my $device;
$device->{role} = "record";
$device->{id} = "record";
$device->{type} = "record";
# Get path (date + room)
unless ($self->{device_control}{$device->{id}}{recordpath}) {
$self->{device_control}{$device->{id}}{recordpath} = set_path($self->{config}{record_path});
$logger->info("Path for $device->{id}: $self->{device_control}{$device->{id}}{recordpath}");
}
# Create the path if it doesn't exist
unless(-d "$self->{device_control}{$device->{id}}{recordpath}") {
my $result = eval { make_path("$self->{device_control}{$device->{id}}{recordpath}") };
if ($result) {
$logger->info("Path created for $device->{id}: $self->{device_control}{$device->{id}}{recordpath}");
post_config();
} else {
# if above threshold then slow down attempts to every 10 seconds
if ( $self->{device_control}{$device->{id}}{runcount} > 5 && (time % 10) != 0 ) {
return;
}
$logger->error("Path creation failed for $device->{id}: $self->{device_control}{$device->{id}}{recordpath}");
$self->{device_control}{$device->{id}}{runcount}++;
# Set device status
$self->{status}{$device->{id}}{type} = $device->{type};
$self->{status}{$device->{id}}{timestamp} = time;
$self->{status}{$device->{id}}{running} = 0;
$self->{status}{$device->{id}}{status} = "not_writeable";
$self->{status}{$device->{id}}{state} = "hard";
post_config();
return;
}
}
if ($self->{dvswitch}{running} == 1) {
run_stop($device);
}
sync();
return;
}
## sync
sub sync {
my $device;
$self->{device_commands}{sync}{command} = "$Bin/station-sync.sh $self->{device_control}{record}{recordpath} $self->{config}{sync}{host} $self->{config}{sync}{path} $self->{config}{room}";
$device->{role} = "sync";
$device->{id} = "sync";
$device->{type} = "internal";
if ($self->{config}{sync}{host} && $self->{config}{sync}{path}) {
run_stop($device);
} else {
# set status
$self->{status}{$device->{id}}{type} = $device->{type};
$self->{status}{$device->{id}}{timestamp} = time;
$self->{status}{$device->{id}}{running} = 0;
$self->{status}{$device->{id}}{status} = "not_configured";
$self->{status}{$device->{id}}{state} = "hard";
}
return;
}
# run, stop or restart a process
sub run_stop {
my ($device) = @_;
my $time = time;
# Build command for execution and save it for future use
unless ($self->{device_commands}{$device->{id}}{command}) {
given ($device->{role}) {
when ("ingest") {
$self->{device_commands}{$device->{id}}{command} = ingest_commands($device->{id},$device->{type});
$logger->info("Command for $device->{id} - $device->{type}: $self->{device_commands}{$device->{id}}{command}");
}
when ("mixer") {
$self->{device_commands}{$device->{id}}{command} = mixer_command();
$logger->info("Command for $device->{id} - $device->{type}: $self->{device_commands}{$device->{id}}{command}");
}
when ("stream") {
$self->{device_commands}{$device->{id}}{command} = stream_command($device->{id},$device->{type});
$logger->info("Command for $device->{id} - $device->{type}: $self->{device_commands}{$device->{id}}{command}");
}
when ("record") {
$self->{device_commands}{$device->{id}}{command} = record_command($device->{id},$device->{type});
$logger->info("Command for $device->{id} - $device->{type}: $self->{device_commands}{$device->{id}}{command}");
}
}
}
# If we're supposed to be running, run.
if (($self->{config}{run} == 1 &&
(! defined $self->{config}{device_control}{$device->{id}}{run} || $self->{config}{device_control}{$device->{id}}{run} == 1)) ||
$device->{type} eq 'internal') {
# The failed service check depends on a run flag being set, if we got here it should be 1.
$self->{config}{device_control}{$device->{id}}{run} = 1;
# Get the running state + pid if it exists
my $state;
if ($self->{device_control}{$device->{id}}{pid}) {
$state = $utils->get_pid_state($self->{device_control}{$device->{id}}{pid});
} else {
$state = $utils->get_pid_command($device->{id},$self->{device_commands}{$device->{id}}{command},$device->{type});
}
unless ($state->{running}) {
# notice process is down, record timestamp when it went down
if ( ! defined $self->{device_control}{$device->{id}}{timestamp} ) {
$self->{device_control}{$device->{id}}{timestamp} = $time;
$self->{device_control}{$device->{id}}{runcount} = 0;
$self->{status}{$device->{id}}{running} = 0;
$self->{status}{$device->{id}}{status} = "starting";
$self->{status}{$device->{id}}{state} = "soft";
$self->{status}{$device->{id}}{type} = $device->{type};
$self->{status}{$device->{id}}{timestamp} = $self->{device_control}{$device->{id}}{timestamp};
$logger->debug("Timestamp and Run Count initialised for $device->{id}");
}
# if above restart threshold then slow down restarts to every 10 seconds
if ( $self->{device_control}{$device->{id}}{runcount} > 5 && ($time % 10) != 0 ) {
return;
}
# log dvswitch start or device connecting
if ($device->{type} eq "mixer") {
$logger->info("Starting DVswitch");
} elsif ($device->{type} eq "internal") {
$logger->info("Starting $device->{id}");
} else {
$logger->info("Connect $device->{id} to DVswitch");
}
# build daemon option
my %proc_opts;
unless ($logger->is_debug()) {
%proc_opts = (
exec_command => $self->{device_commands}{$device->{id}}{command},
);
} else {
%proc_opts = (
child_STDOUT => "/tmp/$device->{id}-STDOUT.log",
child_STDERR => "/tmp/$device->{id}-STDERR.log",
exec_command => $self->{device_commands}{$device->{id}}{command},
);
}
# run process
my $proc = $daemon->Init( \%proc_opts );
# give the process some time to settle
sleep 1;
# Set the running state + pid
$state = $utils->get_pid_command($device->{id},$self->{device_commands}{$device->{id}}{command},$device->{type});
$logger->debug({filter => \&Data::Dumper::Dumper,
value => $state}) if ($logger->is_debug());
# Increase runcount
$self->{device_control}{$device->{id}}{runcount}++;
my $age = $time - $self->{device_control}{$device->{id}}{timestamp};
if ($age > 1 && ! $state->{running}) {
# Log!
$logger->warn("$device->{id} failed to start (count=$self->{device_control}{$device->{id}}{runcount}, died=$age secs ago)");
# Refresh devices
$self->{devices} = $devices->all();
# Force command rebuild
$self->{device_commands}{$device->{id}}{command} = undef;
# post to the api/controller
post_config();
}
}
# If state has changed set it and post the config
if (! defined $self->{device_control}{$device->{id}}{running} ||
($self->{device_control}{$device->{id}}{running} != $state->{running} ||
$self->{device_control}{$device->{id}}{pid} != $state->{pid})) {
# Log
$logger->debug("$device->{id} has changed state");
# Set state
$self->{device_control}{$device->{id}}{pid} = $state->{pid};
$self->{device_control}{$device->{id}}{running} = $state->{running};
# Status Defaults
$self->{status}{$device->{id}}{type} = $device->{type};
$self->{status}{$device->{id}}{timestamp} = $time;
# Status flag
if ($state->{running}) {
$self->{status}{$device->{id}}{running} = 1;
$self->{status}{$device->{id}}{status} = "started";
$self->{status}{$device->{id}}{state} = "hard";
$self->{device_control}{$device->{id}}{timestamp} = $time;
} else {
$self->{status}{$device->{id}}{running} = 0;
$self->{status}{$device->{id}}{status} = "stopped";
$self->{status}{$device->{id}}{state} = "hard";
}
post_config();
}
} elsif (defined $self->{device_control}{$device->{id}}{pid}) {
# Kill The Child
if ($daemon->Kill_Daemon($self->{device_control}{$device->{id}}{pid})) {
# Log
$logger->info("Stop $device->{id}");
# Set device state
$self->{device_control}{$device->{id}}{running} = 0;
$self->{device_control}{$device->{id}}{pid} = undef;
# Set device status
$self->{status}{$device->{id}}{running} = 0;
$self->{status}{$device->{id}}{status} = "stopped";
$self->{status}{$device->{id}}{state} = "hard";
post_config();
}
# Restart Run count and timestamp
$self->{device_control}{$device->{id}}{timestamp} = undef;
$self->{device_control}{$device->{id}}{runcount} = 0;
}
# Set device back to running if a restart was triggered
if ($self->{config}{device_control}{$device->{id}}{run} == 2 && ! $self->{device_control}{$device->{id}}{running}) {
# Log
$logger->info("Restarting $device->{id}");
# Set run back to running
$self->{config}{device_control}{$device->{id}}{run} = 1;
# Reset Run Count and timestamp
$self->{device_control}{$device->{id}}{timestamp} = $time;
$self->{device_control}{$device->{id}}{runcount} = 0;
# Force command rebuild
$self->{device_commands}{$device->{id}}{command} = undef;
# Refresh devices
$self->{devices} = $devices->all();
# Set device status
$self->{status}{$device->{id}}{status} = "restarting";
$self->{status}{$device->{id}}{state} = "hard";
write_config();
post_config();
}
return;
}
## Commands
sub ingest_commands {
my ($id,$type) = @_;
my $command = $self->{commands}{$type};
my $did;
# Some device types require different details
if ($type eq "file") {
$did = $id;
} elsif ($type eq "alsa") {
$self->{devices} = $devices->all();
$did = $self->{devices}{$type}{$id}{alsa};
} else {
$did = $self->{devices}{$type}{$id}{device};
}
my %cmd_vars = (
device => $did,
bin => $Bin,
host => $self->{config}{mixer}{host},
port => $self->{config}{mixer}{port},
);
$command =~ s/\$(\w+)/$cmd_vars{$1}/g;
return $command;
}
sub mixer_command {
my ($id,$type) = @_;
my $command = $self->{commands}{dvswitch};
my %cmd_vars = (
port => $self->{config}{mixer}{port},
);
$command =~ s/\$(\w+)/$cmd_vars{$1}/g;
return $command;
}
sub record_command {
my ($id,$type) = @_;
my $command = $self->{commands}{record};
my %cmd_vars = (
host => $self->{config}{mixer}{host},
port => $self->{config}{mixer}{port},
room => $self->{config}{room},
path => $self->{device_control}{$id}{recordpath},
);
$command =~ s/\$(\w+)/$cmd_vars{$1}/g;
return $command;
}
sub stream_command {
my ($id,$type) = @_;
my $command = $self->{commands}{stream};
my %cmd_vars = (
host => $self->{config}{mixer}{host},
port => $self->{config}{mixer}{port},
id => $id,
shost => $self->{config}{stream}{host},
sport => $self->{config}{stream}{port},
spassword => $self->{config}{stream}{password},
stream => $self->{config}{stream}{stream},
);
$command =~ s/\$(\w+)/$cmd_vars{$1}/g;
return $command;
}
sub set_path {
my ($path) = @_;
my %path_vars = (
room => $self->{config}{room},
date => $self->{date},
);
$path =~ s/\$(\w+)/$path_vars{$1}/g;
return $path;
}
# Get Mac Address
sub getmac {
# This is better, but can break if no eth0. We are only using it as a UID - think of something better.
my $macaddress = `ifdata -ph eth0`;
chomp $macaddress;
return $macaddress;
}
sub blank_station {
my $mixer_ipaddr = `ifdata -pa eth0`;
chomp $mixer_ipaddr;
$mixer_ipaddr =~ s/.$/1/;
my $hostname = `hostname`;
chomp $hostname;
my $room = $hostname;
$room =~ s/-\d+$//;
my $json = <<CONFIG;
{
"roles" :
[
],
"nickname" : "$hostname",
"room" : "$room",
"record_path" : "/localbackup/\$room/\$date",
"mixer" :
{
"port":"1234",
"host":"$mixer_ipaddr",
"loop":"/home/av/eventstreamr/baseimage/video/standby.dv"
},
"sync" :
{
"host":"storage.local",
"path":"/storage"
},
"devices" : "all",
"device_control" :
{
},
"run" : "0",
"stream" :
{
"host" : "",
"port" : "",
"password" : "",
"stream" : ""
}
}
CONFIG
my $config = from_json($json);
return $config;
}
sub blank_settings {
my $json = <<CONFIG;
{
"controller" : "http://10.4.4.10:5001"
}
CONFIG
my $config = from_json($json);
return $config;
}
=pod
=encoding UTF-8
=head1 NAME
station-mgr - station-mgr - Core Station Manager script
=head1 VERSION
version 0.5
=head1 SYNOPSIS
Usage:
station-mgr.pl
=head1 AUTHOR
Leon Wright < techman@cpan.org >
=head1 COPYRIGHT AND LICENSE
This software is Copyright (c) 2014 by Leon Wright.
This is free software, licensed under:
The GNU Affero General Public License, Version 3, November 2007
=cut
__END__
(dev) bofh-sider:~/git/eventstreamr/station $ ps waux|grep dv
leon 19898 2.5 0.1 850956 24560 pts/12 Sl+ 11:35 1:21 dvswitch -h localhost -p 1234
leon 20723 0.0 0.0 4404 612 ? S 12:27 0:00 sh -c ffmpeg -f video4linux2 -s vga -r 25 -i /dev/video0 -target pal-dv - | dvsource-file /dev/stdin -h localhost -p 1234
leon 20724 8.0 0.1 130680 24884 ? S 12:27 0:01 ffmpeg -f video4linux2 -s vga -r 25 -i /dev/video0 -target pal-dv -
leon 20725 0.1 0.0 10796 860 ? S 12:27 0:00 dvsource-file /dev/stdin -h localhost -p 1234
leon 20735 0.0 0.0 9396 920 pts/16 S+ 12:28 0:00 grep --color=auto dv
$VAR1 = {
'commands' => {
'alsa' => 'dvsource-alsa -h $host -p $port hw:$device',
'dv' => 'dvsource-firewire -h $host -p $port -c $device',
'record' => 'dvsink-files $device',
'v4l' => 'ffmpeg -f video4linux2 -s vga -r 25 -i $device -target pal-dv - | dvsource-file /dev/stdin -h $host -p $port',
'stream' => 'dvsink-command -- ffmpeg2theora - -f dv -F 25:2 --speedlevel 0 -v 4 --optimize -V 420 --soft-target -a 4 -c 1 -H 44100 -o - | oggfwd $host $port 8000 $password /$stream',
'file' => 'dvsource-file -l $device',
'dvswitch' => 'dvswitch -h 0.0.0.0 -p $port'
},
'config' => {
'roles' => [
{
'role' => 'ingest'
},
{
'role' => 'mixer'
},
{
'role' => 'stream'
}
],
'nickname' => 'test',
'room' => 'room1',
'devices' => [
{
'name' => 'Chicony Electronics Co. Ltd. ASUS USB2.0 Webcam',
'id' => 'video0',
'type' => 'v4l',
'device' => '/dev/video0'
},
{
'name' => 'C-Media Electronics, Inc. ',
'usbid' => '0d8c:0008',
'id' => '2',
'type' => 'alsa',
'device' => '2'
}
],
'device_control' => {
'video0' => {
'run' => '0'
}
},
'run' => '0',
'macaddress' => '00:15:58:d8:85:c7',
'mixer' => {
'port' => '1234',
'host' => 'localhost'
}
},
'dvswitch' => {
'check' => 1
},
'devices' => {
'array' => [
{
'name' => 'Chicony Electronics Co. Ltd. ASUS USB2.0 Webcam',
'id' => 'video0',
'type' => 'v4l',
'device' => '/dev/video0'
},
{
'name' => 'C-Media Electronics, Inc. ',
'usbid' => '0d8c:0008',
'id' => '2',
'type' => 'alsa',
'device' => '2'
}
],
'dv' => {
'all' => []
},
'alsa' => {
'2' => {
'name' => 'C-Media Electronics, Inc. ',
'usbid' => '0d8c:0008',
'id' => '2',
'type' => 'alsa',
'device' => '2'
},
'all' => [
{
'name' => 'C-Media Electronics, Inc. ',
'usbid' => '0d8c:0008',
'id' => '2',
'type' => 'alsa',
'device' => '2'
}
]
},
'v4l' => {
'video0' => {
'name' => 'Chicony Electronics Co. Ltd. ASUS USB2.0 Webcam',
'id' => 'video0',
'type' => 'v4l',
'device' => '/dev/video0'
},
'all' => [
{
'name' => 'Chicony Electronics Co. Ltd. ASUS USB2.0 Webcam',
'id' => 'video0',
'type' => 'v4l',
'device' => '/dev/video0'
}
]
},
'all' => {
'video0' => {
'name' => 'Chicony Electronics Co. Ltd. ASUS USB2.0 Webcam',
'id' => 'video0',
'type' => 'v4l',
'device' => '/dev/video0'
},
'all' => [
{
'name' => 'C-Media Electronics, Inc. ',
'usbid' => '0d8c:0008',
'id' => '2',
'type' => 'alsa',
'device' => '2'
}
],
'2' => {
'name' => 'C-Media Electronics, Inc. ',
'usbid' => '0d8c:0008',
'id' => '2',
'type' => 'alsa',
'device' => '2'
}
}
}
};