+sub dx_spot
+{
+ my $self = shift;
+ my $dxchan = shift;
+ my $quality = shift;
+ my $cand = shift;
+ my $call = $dxchan->{call};
+
+
+ my $strength = 100; # because it could if we talk about FTx
+ my $saver;
+
+ my %zone;
+ my %qrg;
+ my $respot;
+ my $qra;
+
+ ++$self->{nousers}->{$call};
+ ++$self->{nousers10}->{$call};
+ ++$self->{nousershour}->{$call};
+
+ my $filtered;
+ my $rf = $dxchan->{rbnfilter} || $dxchan->{spotsfilter};
+ my $comment;
+
+ foreach my $r (@$cand) {
+ # $r = [$origin, $qrg, $call, $mode, $s, $t, $utz, $respot, $qra];
+ # Spot::prepare($qrg, $call, $utz, $comment, $origin);
+ next unless ref $r;
+
+ $respot = 1 if $r->[Respot];
+ $qra = $r->[RQra] if !$qra && $r->[RQra] && is_qra($r->[RQra]);
+
+ $comment = sprintf "%-3s %2ddB $quality", $r->[RMode], $r->[RStrength];
+ my $s = $r->[RSpotData]; # the prepared spot
+ $s->[SComment] = $comment; # apply new generated comment
+
+
+ ++$zone{$s->[SZone]}; # save the spotter's zone
+ ++$qrg{$s->[SQrg]}; # and the qrg
+
+
+ # save the lowest strength one
+ if ($r->[RStrength] < $strength) {
+ $strength = $r->[RStrength];
+ $saver = $s;
+ dbg("RBN: STRENGTH spot: $s->[SCall] qrg: $s->[SQrg] origin: $s->[SOrigin] dB: $r->[RStrength] < $strength") if isdbg 'rbnll';
+ }
+
+ if ($rf) {
+ my ($want, undef) = $rf->it($s);
+ dbg("RBN: FILTERING for $call spot: $s->[SCall] qrg: $s->[SQrg] origin: $s->[SOrigin] dB: $r->[RStrength] com: '$s->[SComment]' want: " . ($want ? 'YES':'NO')) if isdbg 'rbnll';
+ next unless $want;
+ $filtered = $s;
+# last;
+ }
+ }
+
+ if ($rf) {
+ $saver = $filtered; # if nothing passed the filter's lips then $saver == $filtered == undef !
+ }
+
+ if ($saver) {
+ my $buf;
+ # create a zone list of spotters
+ delete $zone{$saver->[SZone]}; # remove this spotter's zone (leaving all the other zones)
+ my $z = join ',', sort {$a <=> $b} keys %zone;
+
+ # determine the most likely qrg and then set it
+ my $mv = 0;
+ my $fk;
+ my $c = 0;
+ while (my ($k, $v) = each %qrg) {
+ $fk = $k, $mv = $v if $v > $mv;
+ ++$c;
+ }
+ $saver->[SQrg] = $fk;
+ $saver->[SComment] .= '*' if $c > 1;
+ $saver->[SComment] .= '+' if $respot;
+ $saver->[SComment] .= " Z:$z" if $z;
+ if ($c > 1 && (isdbg('rbnqrg') || isdbg('rbn'))) {
+
+ }
+
+ dbg("RBN: SENDING to $call spot: $saver->[SCall] qrg: $saver->[SQrg] origin: $saver->[SOrigin] $saver->[SComment]") if isdbg 'rbnll';
+ if ($dxchan->{ve7cc}) {
+ my $call = $saver->[SOrigin];
+ $saver->[SOrigin] .= '-#';
+ $buf = VE7CC::dx_spot($dxchan, @$saver);
+ $saver->[SOrigin] = $call;
+ } else {
+ my $call = $saver->[SOrigin];
+ $saver->[SOrigin] = substr($call, 0, 6);
+ $saver->[SOrigin] .= '-#';
+ $buf = $dxchan->format_dx_spot(@$saver);
+ $saver->[SOrigin] = $call;
+ }
+# $buf =~ s/^DX/RB/;
+ $dxchan->local_send('N', $buf);
+
+ ++$self->{nospot};
+ ++$self->{nospot10};
+ ++$self->{nospothour};
+
+ if ($qra) {
+ my $user = DXUser::get_current($saver->[SCall]) || DXUser->new($saver->[SCall]);
+ unless ($user->qra && is_qra($user->qra)) {
+ $user->qra($qra);
+ dbg("RBN: update qra on $saver->[SCall] to $qra");
+ $user->put;
+ }
+ }
+ }
+}
+
+# per second
+sub process
+{
+ foreach my $dxchan (DXChannel::get_all()) {
+ next unless $dxchan->is_rbn;
+
+ # At this point we run the queue to see if anything can be sent onwards to the punter
+ my $now = $main::systime;
+
+ # now run the waiting queue which just contains KEYS ($call|$qrg)
+ foreach my $sp (keys %{$dxchan->{queue}}) {
+ my $cand = $spots->{$sp};
+ unless ($cand && $cand->[CTime]) {
+ dbg "RBN Cand $sp " . ($cand ? 'def' : 'undef') . " [CTime] " . ($cand->[CTime] ? 'def' : 'undef') . " dwell $dwelltime";
+ next;
+ }
+ if ($now >= $cand->[CTime] + $dwelltime ) {
+ # we have a candidate, create qualitee value(s);
+ unless (@$cand > CData) {
+ dbg "RBN: QUEUE key '$sp' MISSING RECORDS, IGNORED" . dd($cand) if isdbg 'rbn';
+ next;
+ }
+ dbg "RBN: QUEUE PROCESSING key: '$sp' $now >= $cand->[CTime]" if isdbg 'rbnqueue';
+ my $r = $cand->[CData];
+ my $quality = @$cand - CData;
+ $quality = 9 if $quality > 9;
+ $cand->[CQual] = $quality if $quality > $cand->[CQual];
+ my $squality = "Q:$cand->[CQual]";
+
+ if ($cand->[CQual] >= $minqual) {
+ if (isdbg('progress')) {
+ my $s = "RBN: SPOT key: '$sp' = $r->[RCall] on $r->[RQrg] by $r->[ROrigin] \@ $r->[RTime] $squality route: $dxchan->{call}";
+ dbg($s);
+ }
+ send_dx_spot($dxchan, $squality, $cand);
+ } elsif (isdbg('rbn')) {
+ my $s = "RBN: SPOT IGNORED(Q $cand->[CQual] < $minqual) key: '$sp' = $r->[RCall] on $r->[RQrg] by $r->[ROrigin] \@ $r->[RTime] $squality route: $dxchan->{call}";
+ dbg($s);
+ }
+
+ # clear out the data and make this now just "spotted", but no further action required until respot time
+ dbg "RBN: QUEUE key '$sp' cleared" if isdbg 'rbn';
+
+ $spots->{$sp} = [$now, $cand->[CQual]];
+ delete $dxchan->{queue}->{$sp};
+ } else {
+ dbg sprintf("RBN: QUEUE key: '$sp' SEND time not yet reached %.1f secs left", $cand->[CTime] + $dwelltime - $now) if isdbg 'rbnqueue';
+ }
+ }
+ }
+
+}
+
+sub per_minute
+{
+ foreach my $dxchan (DXChannel::get_all()) {
+ next unless $dxchan->is_rbn;
+ dbg "RBN:STATS minute $dxchan->{call} raw: $dxchan->{noraw} sent: $dxchan->{norbn} delivered: $dxchan->{nospot} users: " . scalar keys %{$dxchan->{nousers}} if isdbg('rbnstats');
+ if ($dxchan->{noraw} == 0 && $dxchan->{lasttime} > 60) {
+ LogDbg('RBN', "RBN: no input from $dxchan->{call}, disconnecting");
+ $dxchan->disconnect;
+ }
+ $dxchan->{noraw} = $dxchan->{norbn} = $dxchan->{nospot} = 0; $dxchan->{nousers} = {};
+ $runtime{$dxchan->{call}} += 60;
+ }
+
+ # save the spot cache
+ write_cache() unless $main::systime + $startup_delay < $main::systime;;
+}
+
+sub per_10_minute
+{
+ my $count = 0;
+ my $removed = 0;
+ while (my ($k,$cand) = each %{$spots}) {
+ if ($main::systime - $cand->[CTime] > $minspottime*2) {
+ delete $spots->{$k};
+ ++$removed;
+ }
+ else {
+ ++$count;
+ }
+ }
+ dbg "RBN:STATS spot cache remain: $count removed: $removed"; # if isdbg('rbn');
+ foreach my $dxchan (DXChannel::get_all()) {
+ next unless $dxchan->is_rbn;
+ my $nq = keys %{$dxchan->{queue}};
+ dbg "RBN:STATS 10-minute $dxchan->{call} queue: $nq raw: $dxchan->{noraw10} sent: $dxchan->{norbn10} delivered: $dxchan->{nospot10} users: " . scalar keys %{$dxchan->{nousers10}};
+ $dxchan->{noraw10} = $dxchan->{norbn10} = $dxchan->{nospot10} = 0; $dxchan->{nousers10} = {};
+ }
+}
+
+sub per_hour
+{
+ foreach my $dxchan (DXChannel::get_all()) {
+ next unless $dxchan->is_rbn;
+ my $nq = keys %{$dxchan->{queue}};
+ dbg "RBN:STATS hour $dxchan->{call} queue: $nq raw: $dxchan->{norawhour} sent: $dxchan->{norbnhour} delivered: $dxchan->{nospothour} users: " . scalar keys %{$dxchan->{nousershour}};
+ $dxchan->{norawhour} = $dxchan->{norbnhour} = $dxchan->{nospothour} = 0; $dxchan->{nousershour} = {};