You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
418 lines
11 KiB
418 lines
11 KiB
#!/usr/bin/perl -w |
|
|
|
# Heavily modified and adapted by Jason Short |
|
# from http://www.perl.com - Autopilots in Perl |
|
# By Jeffrey Goff on July 12, 2004 |
|
|
|
use strict; |
|
use IO::Socket; |
|
use IO::Select; |
|
$| = 1; |
|
|
|
use constant DATA_num_channels => 113; |
|
use constant DATA_num_elements => 8; |
|
use constant DATA_header_size => 5; |
|
use constant DATA_packet_size => DATA_num_channels * DATA_num_elements + DATA_header_size; |
|
use constant DATA_max_element => 7; |
|
use constant DATA_element_size => (DATA_num_elements + 1) * 4; # 4-byte elements |
|
use constant DATA_inbound_pack => "A4c"; |
|
use constant DATA_inbound_header => ('DATA',0); |
|
|
|
my $rh; |
|
my $x_plane_ip = '127.0.0.1'; |
|
my $ser_port = 5331; |
|
my $receive_port = 49005; |
|
my $transmit_port = 49000; |
|
my $throttle_test = 0; |
|
my $check_a = 0; |
|
my $check_b = 0; |
|
my $count = 0; |
|
|
|
my $roll_out = 0; |
|
my $pitch_out = 0; |
|
my $throttle_out = 0; |
|
my $rudder_out = 0; |
|
my $wp_distance = 0; |
|
my $wp_index = 0; |
|
my $control_mode = 0; |
|
my $control_mode_alpha; |
|
my $bearing_error = 0; |
|
my $alt_error = 0; |
|
my $energy_error = 0; |
|
|
|
my $extraInt = 0; # for debugging |
|
my $int_1 = 0; |
|
my $int_2 = 0; |
|
my $int_3 = 0; |
|
my $int_4 = 0; |
|
|
|
my $diyd_header = "DIYd"; |
|
|
|
my $Xplane_in_sock = IO::Socket::INET->new( |
|
LocalPort => $receive_port, |
|
LocalAddr => $x_plane_ip, |
|
Proto => 'udp') |
|
or die "error creating receive port for $x_plane_ip: $@\n"; |
|
|
|
my $receive = IO::Select->new(); |
|
|
|
|
|
my $Xplane_out_sock = IO::Socket::INET->new( |
|
PeerPort => $transmit_port, |
|
PeerAddr => $x_plane_ip, |
|
Proto => 'udp') |
|
or die "error creating transmit port for $x_plane_ip: $@\n"; |
|
|
|
my $transmit = IO::Select->new(); |
|
|
|
|
|
|
|
#open sockets on 7070 |
|
my $arduSocket = IO::Socket::INET->new( Proto => 'tcp', |
|
PeerAddr => '127.0.0.1', |
|
PeerPort => $ser_port |
|
) or die print "Can not connect to Serial $!"; |
|
#,Type = 'SOCK_STREAM' |
|
|
|
|
|
$receive->add($Xplane_in_sock); |
|
$receive->add($arduSocket); |
|
$transmit->add($Xplane_out_sock); |
|
$arduSocket->autoflush(1); |
|
|
|
|
|
|
|
|
|
my $DATA_buffer = {}; |
|
|
|
# These formats are represented by X-Plane as floating point |
|
my @float_formats = qw( f deg mph pct ); |
|
|
|
|
|
my $DATA_packet = { |
|
# The outer hash reference contains a sparse array of data frames |
|
0 => { |
|
# The inner hash reference contains a sparse array of data elements |
|
0 => { type => 'f', label => 'Frame Rate'}, |
|
}, |
|
|
|
3 => { |
|
6 => { type => 'f', label => 'Air Speed'}, |
|
7 => { type => 'f', label => 'Ground Speed'}, |
|
}, |
|
|
|
8 => { |
|
0 => { type => 'deg', label => 'Pitch'}, |
|
1 => { type => 'deg', label => 'Roll'}, |
|
2 => { type => 'deg', label => 'heading'}, |
|
}, |
|
|
|
11 => { |
|
0 => { type => 'f', label => 'elev'}, |
|
1 => { type => 'f', label => 'aileron'}, |
|
}, |
|
|
|
18 => { |
|
0 => { type => 'deg', label => 'Pitch'}, |
|
1 => { type => 'deg', label => 'Roll'}, |
|
2 => { type => 'deg', label => 'heading'}, |
|
}, |
|
|
|
20 => { |
|
0 => { type => 'deg', label => 'Latitude'}, |
|
1 => { type => 'deg', label => 'Longitude'}, |
|
2 => { type => 'f', label => 'Altitude'}, |
|
}, |
|
|
|
25 => { |
|
0 => { type => 'deg', label => 'Throttle cmd'}, |
|
}, |
|
|
|
26 => { |
|
0 => { type => 'deg', label => 'Throttle'}, |
|
}, |
|
|
|
}; |
|
|
|
|
|
# X-Plane uniformly sends 4-byte floats outbound, |
|
# but accepts a mixture of floats and integers inbound. |
|
|
|
sub create_pack_strings { |
|
for my $row (values %$DATA_packet) { |
|
$row->{unpack} = 'x4'; |
|
$row->{pack} = 'l'; |
|
for my $j (0..DATA_max_element) { |
|
if(exists $row->{$j}) { |
|
my $col = $row->{$j}; |
|
$row->{pack} .= (grep { $col->{type} eq $_ } @float_formats) ? 'f' : 'l'; |
|
$row->{unpack} .= 'f'; |
|
} |
|
else { |
|
$row->{pack} .= 'f'; |
|
$row->{unpack} .= 'x4'; |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
# {{{ Transmit a message |
|
sub transmit_message { |
|
my ($socket,$pack_format,@message) = @_; |
|
my ($server) = $socket->can_write(60); |
|
$server->send(pack($pack_format,@message)); |
|
} |
|
|
|
|
|
sub receive_DATA { |
|
my ($message) = @_; |
|
$DATA_buffer = { }; |
|
for (my $i = 0; $i < (length($message) - &DATA_header_size - 1) / DATA_element_size; $i++) { |
|
my $channel = substr($message, $i * DATA_element_size + DATA_header_size, DATA_element_size); |
|
|
|
my $index = unpack "l", $channel; |
|
next unless exists $DATA_packet->{$index}; |
|
|
|
my $row = $DATA_packet->{$index}; |
|
my @element = unpack $row->{unpack}, $channel; |
|
my $ctr = 0; |
|
for my $j (0..DATA_max_element) { |
|
next unless exists $row->{$j}; |
|
my $col = $row->{$j}; |
|
$DATA_buffer->{$index}{$j} = $element[$ctr]; |
|
$ctr++; |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
# {{{ transmit_DATA |
|
sub transmit_DATA { |
|
my ($socket, @message) = @_; |
|
my $pack_str = DATA_inbound_pack; |
|
for(my $packet = 0; |
|
$packet < @message; |
|
$packet += (DATA_num_elements + 1)) { |
|
$pack_str .= $DATA_packet->{$message[$packet]}{pack}; |
|
} |
|
transmit_message($socket,$pack_str,DATA_inbound_header,@message,0); |
|
} |
|
|
|
|
|
# {{{ Fill in a DATA channel |
|
sub _fill_channel { |
|
my ($packet) = @_; |
|
my @buffer = (-999) x DATA_num_elements; |
|
for(0..7) { |
|
$buffer[$_] = $packet->{$_} if defined $packet->{$_}; |
|
} |
|
return @buffer; |
|
} |
|
|
|
|
|
# {{{ Send outbound messages if there are any |
|
sub transmit_socket { |
|
my ($socket,$ch) = @_; |
|
if($ch eq 'g') { |
|
transmit_DATA($socket, 12, $DATA_buffer->{12}{0} ? 0 : 1,(-999) x 7); |
|
|
|
}elsif($ch eq 't') { |
|
transmit_DATA($socket, 25, $throttle_out,$throttle_out,$throttle_out,$throttle_out,(-999) x 4); |
|
|
|
}elsif($ch eq 'c') { |
|
transmit_DATA($socket, 11, $pitch_out, $roll_out, $rudder_out, (-999), ($roll_out * 5) , -999, -999, -999 ); |
|
|
|
}elsif($ch eq 'j') { |
|
transmit_DATA($socket, 8, $pitch_out, $roll_out, $rudder_out , (-999) x 5); |
|
|
|
}elsif($ch eq 'i') { |
|
my @engine = map { $_ != -999 ? $_ + 0.1 : -999 } _fill_channel($DATA_buffer->{23}); |
|
transmit_DATA( $socket, 23, @engine ); |
|
|
|
}elsif($ch eq 'k') { |
|
my @engine = |
|
map { $_ != -999 ? $_-0.1 : -999 } |
|
_fill_channel($DATA_buffer->{23}); |
|
transmit_DATA( $socket, 23, @engine ); |
|
} |
|
} |
|
|
|
|
|
sub parseXplane { |
|
|
|
#convert data |
|
my $lat = int($DATA_buffer->{20}{0} * 10000000); |
|
my $lng = int($DATA_buffer->{20}{1} * 10000000); |
|
my $altitude = int($DATA_buffer->{20}{2} * 3.048); # altitude to meters |
|
my $speed = int($DATA_buffer->{3}{7} * 044.704); # spped to m/s * 100 |
|
my $airspeed = int($DATA_buffer->{3}{6} * 044.704); # speed to m/s * 100 |
|
my $pitch = int($DATA_buffer->{18}{0} * 100); |
|
my $roll = int($DATA_buffer->{18}{1} * 100); |
|
my $heading = int($DATA_buffer->{18}{2} * 100); |
|
|
|
# format and publish the IMU style data to the Ardupilot |
|
my $outgoing = pack("CCs<4", 8,4, $roll, $pitch, $heading, $airspeed); |
|
$check_a = $check_b = 0; |
|
for( split(//,$outgoing) ) |
|
{ |
|
$check_a += ord; |
|
$check_a %= 256; |
|
$check_b += $check_a; |
|
$check_b %= 256; |
|
#print ord . "\n"; |
|
} |
|
$outgoing = pack("a4CCs<4CC","DIYd", 8,4, $roll, $pitch, $heading, $airspeed, $check_a,$check_b); |
|
$arduSocket->send($outgoing); |
|
|
|
$count++; |
|
if ($count == 10){ |
|
# count of 10 = 5 hz, 50 = 1hz |
|
$count = 0; |
|
$outgoing = pack("CCl<2s<3", 14,3, $lng, $lat, $altitude, $speed, $heading); |
|
$check_a = $check_b = 0; |
|
for( split(//,$outgoing) ) |
|
{ |
|
$check_a += ord; |
|
$check_a %= 256; |
|
$check_b += $check_a; |
|
$check_b %= 256; |
|
#print ord . "\n"; |
|
} |
|
$outgoing = pack("a4CCl<2s<3CC","DIYd", 14,3, $lng, $lat, $altitude, $speed, $heading, $check_a, $check_b); |
|
$arduSocket->send($outgoing); |
|
} |
|
#print "lat = $lat, long = $lng, alt = $altitude, ASpeed = $airspeed, $heading\n"; |
|
#print "alt = $altitude,speed = $speed \n"; |
|
|
|
$count = 0 if ($count > 50); |
|
} |
|
|
|
sub parseMessage |
|
{ |
|
my ($data) = @_; |
|
my @out = unpack("s8C2",$data); |
|
|
|
$roll_out = $out[0] *1 / 4500 if (defined $out[0]); |
|
$roll_out = 1 if($roll_out > 1); |
|
$roll_out = -1 if($roll_out < -1); |
|
|
|
$pitch_out = $out[1] *1 / 4500 if (defined $out[1]); |
|
$pitch_out = 1 if($pitch_out > 1); |
|
$pitch_out = -1 if($pitch_out < -1); |
|
|
|
|
|
$throttle_out = $out[2] / 100 if (defined $out[2]); |
|
|
|
|
|
$rudder_out = $out[3] * 1 / 4500 if (defined $out[3]); |
|
$rudder_out = 1 if($rudder_out > 1); |
|
$rudder_out = -1 if($rudder_out < -1); |
|
|
|
# normal reading |
|
$wp_distance = $out[4] if (defined $out[4]); |
|
$bearing_error = $out[5] / 100 if (defined $out[5]); |
|
$alt_error = $out[6] if (defined $out[6]); |
|
$energy_error = $out[7] if (defined $out[7]); |
|
$wp_index = $out[8] if (defined $out[8]); |
|
$control_mode = $out[9] if (defined $out[9]); |
|
|
|
#debugging - send any three ints for debugging! |
|
$int_1 = $out[4] * 1 if (defined $out[4]); |
|
$int_2 = $out[5] * 1 if (defined $out[5]); |
|
$int_3 = $out[6] * 1 if (defined $out[6]); |
|
$int_4 = $out[7] * 1 if (defined $out[7]); |
|
|
|
#output_int((int)airspeed); // 4 bytes 8,9 |
|
#output_int((int)airspeed_error); // 5 bytes 10,11 |
|
#output_int((int)altitude_error); // 6 bytes 12,13 |
|
#output_int((int)energy_error); // 7 bytes 14,15 |
|
|
|
SWITCH: { |
|
$control_mode == 0 && do { $control_mode_alpha = "Manual"; last SWITCH; }; |
|
$control_mode == 1 && do { $control_mode_alpha = "CIRCLE"; last SWITCH; }; |
|
$control_mode == 2 && do { $control_mode_alpha = "STABILIZE"; last SWITCH; }; |
|
$control_mode == 5 && do { $control_mode_alpha = "FLY_BY_WIRE_A"; last SWITCH; }; |
|
$control_mode == 6 && do { $control_mode_alpha = "FLY_BY_WIRE_B"; last SWITCH; }; |
|
$control_mode == 10 && do { $control_mode_alpha = "AUTO"; last SWITCH; }; |
|
$control_mode == 11 && do { $control_mode_alpha = "RTL"; last SWITCH; }; |
|
$control_mode == 12 && do { $control_mode_alpha = "LOITER"; last SWITCH; }; |
|
$control_mode == 13 && do { $control_mode_alpha = "TAKEOFF"; last SWITCH; }; |
|
$control_mode == 14 && do { $control_mode_alpha = "LAND"; last SWITCH; }; |
|
} |
|
|
|
$bearing_error = sprintf("%.1f", $bearing_error); |
|
if ($count){ |
|
#printf("roll: %.3f, pitch: %.3f, thr: %.3f, rud: %.3f \n", $roll_out,$pitch_out,$throttle_out,$rudder_out); |
|
print "wp_dist:$wp_distance \tbearing_err:$bearing_error \talt_error:$alt_error \tenergy_err:$energy_error \tWP:$wp_index \tmode:$control_mode_alpha\r"; |
|
#print "wp_dist:$wp_distance \tbearing_err:$bearing_error \tloiter_sum:$int_3 \tLoiter total:$int_4 \tWP:$wp_index \tmode:$control_mode_alpha\r"; |
|
|
|
#print "next_WP.alt:$int_1 \toffset_altitude:$int_2 \talt_error:$alt_error \ttarget_altitude:$int_4 \tWP:$wp_index \tmode:$control_mode_alpha\n"; |
|
#print "test_alt:$int_1 \ttarget_altitude:$int_2 \tnext_wp.alt:$int_3 \toffset_altitude:$int_4 \tWP:$wp_index \tmode:$control_mode_alpha\n"; |
|
#print "throttle_cruise: $int_1, landing_pitch: $int_2, throttle: $throttle_out, altitude_error: $int_3, WP: $wp_index, mode: $control_mode\n"; |
|
} |
|
transmit_socket($transmit,'t'); |
|
transmit_socket($transmit,'j'); |
|
transmit_socket($transmit,'c'); |
|
} |
|
|
|
sub readline { |
|
my ($rh) = @_; |
|
my $temp = ""; |
|
my $step = 0; |
|
my $message = ""; |
|
|
|
while (1) { |
|
$rh->recv($message,1); |
|
$temp .= $message; |
|
|
|
if ($temp =~ /^AAA/) { |
|
$step++; |
|
if ($temp =~ "\n" && $step >= 19) { last; } # data |
|
} |
|
if ($temp =~ /\n/ && $step == 0) { # normal message |
|
last; |
|
} |
|
} |
|
return $temp; |
|
} |
|
|
|
sub main_loop { |
|
my ($receive,$transmit) = @_; |
|
my $recv_data = ""; |
|
|
|
while(1) { |
|
my ($rh_set) = IO::Select->select($receive, undef, undef, .1); |
|
|
|
|
|
foreach $rh (@$rh_set) { |
|
if ($rh == $Xplane_in_sock) { |
|
my $message; |
|
$rh->recv($message,DATA_packet_size); |
|
receive_DATA($message); |
|
parseXplane(); |
|
|
|
}elsif ($rh == $arduSocket) { |
|
my $message = ''; |
|
|
|
$message = &readline($rh); |
|
|
|
if($message =~ '^AAA'){ |
|
$message = substr $message, 3, 18; |
|
parseMessage($message); |
|
}elsif($message =~ '^MSG'){ |
|
print "$message\n"; |
|
}else{ |
|
#print "er:$message \n"; |
|
} |
|
$rh->flush(); |
|
#print "$message \n"; |
|
} |
|
} |
|
|
|
} |
|
} |
|
|
|
create_pack_strings(); |
|
main_loop($receive, $transmit);
|
|
|