package POEDaemon::EventSystem; use strict; use warnings FATAL => 'all'; no warnings 'redefine'; use Data::Dumper; use POE; use POEDaemon; #eval #{ # require Time::Stopwatch; #}; # #my $have_time_stopwatch = 1 unless $@; sub states { return $_[0], [qw( eventsystem_init eventsystem_gc eventsystem_input eventsystem_input_clientdisconnect eventsystem_input_delayrepeat eventsystem_output eventsystem_output_remotegpio )]; } sub eventsystem_init { my ($kernel, $heap, $state) = @_[KERNEL, HEAP, STATE]; #$kernel->yield('eventsystem_gc'); } sub eventsystem_gc { my ($kernel, $heap, $state) = @_[KERNEL, HEAP, STATE]; unless ($heap->{shutdown}) { $kernel->delay($state => 10); } } sub eventsystem_input { my ($kernel, $session, $heap, $args) = @_[KERNEL, SESSION, HEAP, ARG0]; #my $stopwatch; # # #tie $stopwatch, 'Time::Stopwatch' if $have_time_stopwatch; if (log_enabled) { $args->{_start_time} = time_hires; } my $only_set_heapvars = delete $args->{_only_set_heapvars}; my $type = $args->{type}; my $cn = $args->{cn}; my $pin = $args->{pin}; my $adc = $args->{adc}; my $name = $args->{name}; my $value = $args->{value}; my $raw = $args->{raw}; my $mv = $args->{mv}; my $datetime = $args->{datetime}; my $time = $args->{time}; $kernel->yield(edbi_eventlog => { event_direction => 'input', event_args => $args, }); return unless $type; return unless defined $adc || defined $datetime || (defined $value && ($value == GPIO_HIGH || $value == GPIO_LOW)); my $map_key_input; if ($type eq 'remote-gpio') { $map_key_input = join ':', $type, $cn, $pin; } elsif ($type eq 'remote-gpiointerrupt') { $map_key_input = join ':', $type, $cn, $pin; } elsif ($type eq 'remote-adc') { $map_key_input = join ':', $type, $cn, $adc; } elsif ($type eq 'fastcgi') { $map_key_input = join ':', $type, $name; } elsif ($type eq 'sun') { $map_key_input = $type; } elsif ($type eq 'time') { $map_key_input = $type; } my $map_items = cfg->{eventmap}->{$map_key_input}; return unless ref $map_items eq 'HASH'; my $value_time = { value => $value, time => $time, }; my $raw_mv_time = { raw => $raw, mv => $mv, time => $time, }; my $datetime_time = { datetime => $datetime, time => $time, }; if ($type eq 'remote-gpio') { $heap->{eventsystem}->{input}->{$type}->{$cn}->{$pin} = $value_time; } elsif ($type eq 'remote-gpiointerrupt') { $heap->{eventsystem}->{input_interrupt}->{$type}->{$cn}->{$pin} = $value_time; } elsif ($type eq 'remote-adc') { $heap->{eventsystem}->{input}->{$type}->{$cn}->{$adc} = $raw_mv_time; } elsif ($type eq 'fastcgi') { $heap->{eventsystem}->{input}->{$type}->{$name} = $value_time; } elsif ($type eq 'sun') { $heap->{eventsystem}->{input}->{$type} = $value_time; } elsif ($type eq 'time') { $heap->{eventsystem}->{input}->{$type} = $datetime_time; } return if $only_set_heapvars; if ($type eq 'remote-adc') { #log_enabled && logline "args:\n%s", Dumper $args; foreach my $map_item (keys %$map_items) { next unless defined $map_item; #log_enabled && logline "map_item = '%s'", $map_item; my $matched; if ($map_item =~ /^(\d{1,4}(?:\.\d{1,6})?)-(\d{1,4}(?:\.\d{1,6})?)$/) { my $min = $1; my $max = $2; if ($mv >= $min && $mv <= $max) { #log_enabled && logline 'in range'; $matched = 'in range'; } } elsif ($map_item =~ /^!(\d{1,4}(?:\.\d{1,6})?)-(\d{1,4}(?:\.\d{1,6})?)$/) { my $min = $1; my $max = $2; unless ($mv >= $min && $mv <= $max) { #log_enabled && logline 'not in range'; $matched = 'not in range'; } } elsif ($map_item =~ /^>(\d{1,4}(?:\.\d{1,6})?)$/) { my $max = $1; if ($mv > $max) { #log_enabled && logline 'over max'; $matched = 'over max'; } } elsif ($map_item =~ /^<(\d{1,4}(?:\.\d{1,6})?)$/) { my $min = $1; if ($mv < $min) { #log_enabled && logline 'under min'; $matched = 'under min'; } } next unless $matched; #log_enabled && logline 'matched'; my $map_items2 = $map_items->{$map_item}; #log_enabled && logline "map_items2:\n%s", Dumper $map_items2; next unless ref $map_items2 eq 'ARRAY'; foreach my $map_items3 (@$map_items2) { #log_enabled && logline "map_items3:\n%s", Dumper $map_items3; next unless ref $map_items3 eq 'ARRAY'; foreach my $map_item3 (@$map_items3) { next unless defined $map_item3; #log_enabled && logline "map_item3 = '%s'", $map_item3; next unless $map_item3 =~ /^poe-state:(\S+)$/i; my $target_state = $1; log_enabled && logline "source '%s', map item '%s', match '%s', found target state '%s'", $map_key_input, $map_item, $matched || 'n/a', $target_state; $kernel->yield($target_state); } } } return; } if ($type eq 'time') { foreach my $map_item (keys %$map_items) { next unless defined $map_item; my $matched; if ($map_item =~ /^[\d\.]{4}-[\d\.]{2}-[\d\.]{2}\s+[\d\.]{2}:[\d\.]{2}:[\d\.]{2}$/) { if ($datetime =~ /^$map_item$/) { $matched = 'equals'; } } next unless $matched; my $map_items2 = $map_items->{$map_item}; next unless ref $map_items2 eq 'ARRAY'; foreach my $map_items3 (@$map_items2) { next unless ref $map_items3 eq 'ARRAY'; foreach my $map_item3 (@$map_items3) { next unless defined $map_item3 && $map_item3 =~ /^poe-state:(\S+)$/i; my $target_state = $1; log_enabled && logline "source '%s', map item '%s', match '%s', found target state '%s'", $map_key_input, $map_item, $matched || 'n/a', $target_state; $kernel->yield($target_state); } } } return; } my $map_items2 = $map_items->{$value}; return unless ref $map_items2 eq 'ARRAY'; foreach my $map_item (@$map_items2) { next unless defined $map_item; if ($map_item =~ /^(?:latch|alarm)$/) { next unless $type =~ /^remote-gpio(?:interrupt)?$/; unless ($heap->{eventsystem}->{$map_item}->{$type}->{$cn}->{$pin}->{time}) { $heap->{eventsystem}->{$map_item}->{$type}->{$cn}->{$pin} = $value_time; #log_enabled && logline "---> %s <--- ... '%s'", uc $map_item, $map_key_input; $kernel->yield(eventsystem_output => { out_type => $map_item, type => $type, cn => $cn, pin => $pin, value => $value, time => $time, }); if ($map_item eq 'alarm') { $kernel->yield(evsys_handler_alarm_activate => { orig_args => $args, msg => sprintf("alarm, '%s' = '%s'", $map_key_input, $value), }); } } next; } next unless ref $map_item eq 'ARRAY'; my ($map_key, $map_value, $map_extra) = @$map_item; next unless $map_key; my ($map_type, $map_cn, $map_pin) = split ':', $map_key; next unless defined $map_type && defined $map_cn && defined $map_pin && defined $map_value; next unless $map_type eq 'remote-gpio'; my $remotegpio_args = { #_stopwatch => $stopwatch, _start_time => $args->{_start_time}, _source_name => $name, type => $map_type, cn => $map_cn, pin => $map_pin, value => $map_value, time => $time, }; my $target_event = [ 'eventsystem_output_remotegpio', $remotegpio_args ]; my $alarm_id_on = sprintf "%s-%s-on", $map_key_input, $map_key; my $alarm_id_off = sprintf "%s-%s-off", $map_key_input, $map_key; next unless ref $target_event eq 'ARRAY' && $target_event->[0]; my $target_args = { type => $type, cn => $cn, pin => $pin, name => $name, value => $value, time => $time, event => $target_event, }; if (ref $map_extra eq 'HASH') { foreach my $opt (qw(wait repeat repeatlimit)) { my $optval = $map_extra->{$opt}; next unless defined $optval && $optval =~ /^\d+(?:\.\d+)?$/; $target_args->{$opt} = $optval; } } if ($value == GPIO_HIGH) { $target_args->{alarm_id} = $alarm_id_on; } elsif ($value == GPIO_LOW) { $target_args->{alarm_id} = $alarm_id_off; } if ($target_args->{wait}) { if ($value == GPIO_HIGH) { $kernel->alarm_remove(delete $heap->{alarms}->{$alarm_id_off}); $heap->{alarms}->{$alarm_id_on} = $kernel->alarm_set(eventsystem_input_delayrepeat => time_hires + $target_args->{wait} => $target_args); } elsif ($value == GPIO_LOW) { $kernel->alarm_remove(delete $heap->{alarms}->{$alarm_id_on}); $heap->{alarms}->{$alarm_id_off} = $kernel->alarm_set(eventsystem_input_delayrepeat => time_hires + $target_args->{wait} => $target_args); } } else { my $last_run; if (defined $target_args->{repeatlimit} && $target_args->{repeatlimit} =~ /^\d+$/) { $last_run = 1 if $target_args->{repeatlimit} <= 0; $target_args->{repeatlimit}--; } unless ($last_run) { $kernel->call($session => $target_args->{event}->[0] => $target_args->{event}->[1]); if ($target_args->{repeat}) { $kernel->alarm_set(eventsystem_input_delayrepeat => time_hires + $target_args->{repeat} => $target_args); } } } } } sub eventsystem_input_clientdisconnect { my ($kernel, $heap, $args) = @_[KERNEL, HEAP, ARG0]; my $cn = $args->{orig_args}->{old_client}->{cn}; my $wheel_error = $args->{orig_args}->{wheel_error}; my $reason = $args->{orig_args}->{reason}; my $shutdown_reason = $args->{orig_args}->{old_client}->{shutdown_reason}; my $ip = $args->{orig_args}->{old_client}->{client_addr}; my $client_disconnect_reason = join(', ', ( (ref $wheel_error eq 'ARRAY' ? join ': ', @$wheel_error : ()), (defined $reason ? $reason : ()), (defined $shutdown_reason ? $shutdown_reason : ()), )); $kernel->yield(eventsystem_input => { type => 'clientdisconnect', cn => $cn, reason => $client_disconnect_reason, ip => $ip, time => time_hires, }); #log_enabled && logline "shutdown_reason=\n%s", Dumper $shutdown_reason; return unless $cn; my $alarm_client_found; foreach my $curr_key (sort keys %{cfg->{eventmap}}) { next unless ref cfg->{eventmap}->{$curr_key} eq 'HASH'; next unless $curr_key =~ /^remote-gpio(?:interrupt)?:(.+):/; next unless $1 && $1 eq $cn; foreach my $curr_key2 (sort keys %{cfg->{eventmap}->{$curr_key}}) { next unless ref cfg->{eventmap}->{$curr_key}->{$curr_key2} eq 'ARRAY'; foreach my $curr_val (sort @{cfg->{eventmap}->{$curr_key}->{$curr_key2}}) { if (defined $curr_val && $curr_val eq 'alarm') { $alarm_client_found = 1; } last if $alarm_client_found; } last if $alarm_client_found; } last if $alarm_client_found; } if ($alarm_client_found) { unless ($heap->{eventsystem}->{alarm_disconnect}->{$cn}->{time}) { #log_enabled && logline "shutdown_reason=\n%s", Dumper $shutdown_reason; $heap->{eventsystem}->{alarm_disconnect}->{$cn} = { time => time_hires, (defined $wheel_error ? (wheel_error => $wheel_error ) : ()), (defined $reason ? (reason => $reason ) : ()), (defined $shutdown_reason ? (shutdown_reason => $shutdown_reason) : ()), }; my $msg = sprintf "client cn '%s' disconnected%s%s%s", $cn, (ref $wheel_error eq 'ARRAY' ? sprintf ", '%s'", join ': ', @$wheel_error : ''), (defined $reason ? sprintf ", '%s'", $reason : ''), (defined $shutdown_reason ? sprintf ", '%s'", $shutdown_reason : ''); #log_enabled && logline "msg = '%s'", $msg; $kernel->yield(eventsystem_output => { out_type => 'alarm-disconnect', cn => $cn, reason => $client_disconnect_reason, ip => $ip, time => time_hires, }); $kernel->yield(evsys_handler_alarm_activate => { msg => $msg, }); } } } sub eventsystem_input_delayrepeat { my ($kernel, $session, $heap, $state, $args) = @_[KERNEL, SESSION, HEAP, STATE, ARG0]; my $type = $args->{type}; my $cn = $args->{cn}; my $pin = $args->{pin}; my $name = $args->{name}; my $value = $args->{value}; my $time = $args->{time}; my $alarm_id = $args->{alarm_id}; my $event = $args->{event}; my $repeat = $args->{repeat}; $kernel->alarm_remove(delete $heap->{alarms}->{$alarm_id}); my $current; if ($type eq 'remote-gpio') { $current = $heap->{eventsystem}->{input}->{$type}->{$cn}->{$pin}; } elsif ($type eq 'fastcgi') { $current = $heap->{eventsystem}->{input}->{$type}->{$name}; } elsif ($type eq 'sun') { $current = $heap->{eventsystem}->{input}->{$type}; } my $curr_value = $current->{value}; my $curr_time = $current->{time}; return unless $curr_value eq $value && $curr_time eq $time; if (defined $args->{repeatlimit} && $args->{repeatlimit} =~ /^\d+$/) { return if $args->{repeatlimit} <= 0; $args->{repeatlimit}--; } $kernel->call($session => $event->[0] => $event->[1]); return unless $repeat; $kernel->alarm_set($state => time_hires + $repeat => $args); } sub eventsystem_output { my ($kernel, $args) = @_[KERNEL, ARG0]; $kernel->yield(edbi_eventlog => { event_direction => 'output', event_args => $args, }); } sub eventsystem_output_remotegpio { my ($kernel, $session, $heap, $args) = @_[KERNEL, SESSION, HEAP, ARG0]; #log_enabled && logline "begin: stopwatch = %s", ($have_time_stopwatch ? sprintf "%.3fus / %.3fms", $args->{_stopwatch} * 1000000, $args->{_stopwatch} * 1000 : 'n/a'); if (exists $args->{_start_time} && defined $args->{_start_time}) { my $time = time_hires - $args->{_start_time}; log_enabled && logline "begin: stopwatch = %.3fus / %.3fms", $time * 1000000, $time * 1000; } #log_enabled && logline "\n%s", Dumper $args; my $source_name = delete $args->{_source_name}; my $type = $args->{type}; my $cn = $args->{cn}; my $pin = $args->{pin}; my $value = $args->{value}; my $time = $args->{time}; $kernel->yield(edbi_eventlog => { event_direction => 'output', event_args => $args, }); unless (defined $cn && defined $pin && defined $value) { log_enabled && logline 'args error'; return; } $heap->{eventsystem}->{output}->{'remote-gpio'}->{$cn} = {} unless exists $heap->{eventsystem}->{output}->{'remote-gpio'}->{$cn}; my $remotegpio_out = $heap->{eventsystem}->{output}->{'remote-gpio'}->{$cn}; if ($value eq GPIO_TOGGLE) { my $prev_value = $remotegpio_out->{$pin}->{value}; if (!defined $prev_value || $prev_value == GPIO_LOW) { $value = GPIO_HIGH; } elsif ($prev_value == GPIO_HIGH) { $value = GPIO_LOW; } } return unless $value == GPIO_HIGH || $value == GPIO_LOW; return if $remotegpio_out->{$pin}->{value} && $remotegpio_out->{$pin}->{value} == $value; $remotegpio_out->{$pin} = { value => $value, time => $time, }; my $map_key = join ':', $type, $cn, $pin; my $reverse_events_executed = {}; foreach my $curr_key (keys %{cfg->{eventmap}}) { next unless ref cfg->{eventmap}->{$curr_key} eq 'HASH'; foreach my $curr_key2 (keys %{cfg->{eventmap}->{$curr_key}}) { next unless ref cfg->{eventmap}->{$curr_key}->{$curr_key2} eq 'ARRAY'; foreach my $curr_val (@{cfg->{eventmap}->{$curr_key}->{$curr_key2}}) { next unless ref $curr_val eq 'ARRAY' && defined $curr_val->[0] && $curr_val->[0] eq $map_key && $curr_key =~ /^(fastcgi):(.+)$/; my $rev_type = $1; my $rev_name = $2; next if defined $source_name && $rev_name eq $source_name; next if exists $reverse_events_executed->{$rev_name}; log_enabled && logline "source_name = %s, rev_type = '%s', rev_name = '%s'", dumper_oneline($source_name), $rev_type, $rev_name; $kernel->call($session => eventsystem_input => { _only_set_heapvars => 1, type => $rev_type, name => $rev_name, value => $value, time => $time, }); $reverse_events_executed->{$rev_name} = 1; } } } my $client = $heap->{tcpserver}->{connections_by_cn}->{$cn}; return unless $client; return unless $client->{wheel}; my $wheel_id = $client->{wheel_id}; my $poeclient_pins_out = $client->{poeclient_pins}->{out}; return unless $poeclient_pins_out->{$pin}; my $output = sprintf "%s gpio %s=%s", POESERVER_CMD_PREFIX, $pin, $value; return unless $output; $kernel->call($session => tcpserver_output => { wheel_id => $wheel_id, output => $output, use_flush => 1, }); #log_enabled && logline "end: stopwatch = %s", ($have_time_stopwatch ? sprintf "%.3fus / %.3fms", $args->{_stopwatch} * 1000000, $args->{_stopwatch} * 1000 : 'n/a'); if (exists $args->{_start_time} && defined $args->{_start_time}) { my $time = time_hires - $args->{_start_time}; log_enabled && logline "end: stopwatch = %.3fus / %.3fms", $time * 1000000, $time * 1000; } } 1;