1049 lines
31 KiB
C++
1049 lines
31 KiB
C++
// AV400WSim.cxx -- Garmin 400 series protocal class. This AV400WSim
|
|
// protocol generates the set of "simulator" commands a garmin 400 WAAS
|
|
// series gps would expect as input in simulator mode. The AV400W
|
|
// protocol parses the set of commands that a garmin 400W series gps
|
|
// would emit.
|
|
//
|
|
// The Garmin WAAS GPS uses 2 serial channels to communicate with the
|
|
// simulator. These 2 channels are represented by the FGAV400WSimA and
|
|
// the FGAV400WSimB classes. The "A" channel is similar to the previous
|
|
// AVSim400 protocol. The "B" channel is considered the "GPS" channel and
|
|
// uses a different protocol than the "A" channel. The GPS unit expects
|
|
// input on the "B" channel at two different frequencies (1hz and 5hz,
|
|
// normally). The "B" channel also expects responses to certain output
|
|
// messages.
|
|
//
|
|
// Original AV400Sim code Written by Curtis Olson, started Janauary 2009.
|
|
// This AV400W code written by Bruce Hellstrom, March 2011.
|
|
//
|
|
// Copyright (C) 2009 Curtis L. Olson - http://www.flightgear.org/~curt
|
|
// Copyright (c) 2011 Bruce Hellstrom - http://www.celebritycc.com
|
|
//
|
|
// This program is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU General Public License as
|
|
// published by the Free Software Foundation; either version 2 of the
|
|
// License, or (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful, but
|
|
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
// General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program; if not, write to the Free Software
|
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
//
|
|
//
|
|
// $Id$
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#include <cstdio>
|
|
|
|
#include <simgear/debug/logstream.hxx>
|
|
#include <simgear/math/sg_geodesy.hxx>
|
|
#include <simgear/io/iochannel.hxx>
|
|
#include <simgear/timing/sg_time.hxx>
|
|
|
|
#include <FDM/flightProperties.hxx>
|
|
#include <Main/fg_props.hxx>
|
|
#include <Main/globals.hxx>
|
|
|
|
#include "AV400WSim.hxx"
|
|
|
|
FGAV400WSimA::FGAV400WSimA() {
|
|
}
|
|
|
|
FGAV400WSimA::~FGAV400WSimA() {
|
|
}
|
|
|
|
|
|
// generate AV400WSimA message
|
|
bool FGAV400WSimA::gen_message() {
|
|
// cout << "generating garmin message" << endl;
|
|
|
|
char msg_h[32], msg_i[32], msg_j[32], msg_k[32], msg_l[32];
|
|
//char msg_type2[256];
|
|
|
|
double alt;
|
|
|
|
// create msg_h
|
|
double obs = fgGetDouble( "/instrumentation/nav[0]/radials/selected-deg" );
|
|
sprintf( msg_h, "h%04d\r\n", (int)(obs*10) );
|
|
|
|
// create msg_i
|
|
double fuel = fgGetDouble( "/consumables/fuel/total-fuel-gals" );
|
|
if ( fuel > 999.9 ) { fuel = 999.9; }
|
|
sprintf( msg_i, "i%04.0f\r\n", fuel*10.0 );
|
|
|
|
// create msg_j
|
|
double gph = fgGetDouble( "/engines/engine[0]/fuel-flow-gph" );
|
|
gph += fgGetDouble( "/engines/engine[1]/fuel-flow-gph" );
|
|
gph += fgGetDouble( "/engines/engine[2]/fuel-flow-gph" );
|
|
gph += fgGetDouble( "/engines/engine[3]/fuel-flow-gph" );
|
|
if ( gph > 999.9 ) { gph = 999.9; }
|
|
sprintf( msg_j, "j%04.0f\r\n", gph*10.0 );
|
|
|
|
// create msg_k
|
|
sprintf( msg_k, "k%04d%02d%02d%02d%02d%02d\r\n",
|
|
fgGetInt( "/sim/time/utc/year"),
|
|
fgGetInt( "/sim/time/utc/month"),
|
|
fgGetInt( "/sim/time/utc/day"),
|
|
fgGetInt( "/sim/time/utc/hour"),
|
|
fgGetInt( "/sim/time/utc/minute"),
|
|
fgGetInt( "/sim/time/utc/second") );
|
|
|
|
// create msg_l
|
|
alt = fgGetDouble( "/instrumentation/pressure-alt-ft" );
|
|
if ( alt > 99999.0 ) { alt = 99999.0; }
|
|
sprintf( msg_l, "l%05.0f\r\n", alt );
|
|
|
|
// sentence type 2
|
|
//sprintf( msg_type2, "w01%c\r\n", (char)65 );
|
|
|
|
// assemble message
|
|
string sentence;
|
|
sentence += '\002'; // STX
|
|
sentence += msg_h; // obs heading in deg (*10)
|
|
sentence += msg_i; // total fuel in gal (*10)
|
|
sentence += msg_j; // fuel flow gph (*10)
|
|
sentence += msg_k; // date/time (UTC)
|
|
sentence += msg_l; // pressure altitude
|
|
//sentence += msg_type2; // type2 message
|
|
sentence += '\003'; // ETX
|
|
|
|
// cout << sentence;
|
|
length = sentence.length();
|
|
// cout << endl << "length = " << length << endl;
|
|
strncpy( buf, sentence.c_str(), length );
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// parse AV400SimA message
|
|
bool FGAV400WSimA::parse_message() {
|
|
SG_LOG( SG_IO, SG_INFO, "parse AV400WSimA message" );
|
|
|
|
string msg = buf;
|
|
msg = msg.substr( 0, length );
|
|
SG_LOG( SG_IO, SG_INFO, "entire message = " << msg );
|
|
|
|
string ident = msg.substr(0, 1);
|
|
if ( ident == "i" ) {
|
|
string side = msg.substr(1,1);
|
|
string num = msg.substr(2,3);
|
|
if ( side == "-" ) {
|
|
fgSetDouble("/instrumentation/av400w/cdi-deflection", 0.0);
|
|
}
|
|
else {
|
|
int pos = atoi(num.c_str());
|
|
if ( side == "L" ) {
|
|
pos *= -1;
|
|
}
|
|
fgSetDouble("/instrumentation/av400w/cdi-deflection",
|
|
(double)pos / 10.0);
|
|
//printf( "i, %s%s, %f\n", side.c_str(), num.c_str(), (double)(pos / 10.0) );
|
|
}
|
|
}
|
|
else if ( ident == "j" ) {
|
|
string side = msg.substr(1,1);
|
|
string num = msg.substr(2,3);
|
|
if ( side == "-" ) {
|
|
fgSetDouble("/instrumentation/av400w/gs-deflection", 0.0);
|
|
}
|
|
else {
|
|
int pos = atoi(num.c_str());
|
|
if ( side == "B" ) {
|
|
pos *= -1;
|
|
}
|
|
// convert glideslope to -3.5 to 3.5
|
|
fgSetDouble("/instrumentation/av400w/gs-deflection",
|
|
(double)pos / 28.57);
|
|
//printf( "j, %s%s, %f\n", side.c_str(), num.c_str(), (double)(pos / 28.57) );
|
|
}
|
|
}
|
|
else if ( ident == "k" ) {
|
|
string ind = msg.substr(1,1);
|
|
if ( ind == "T" ) {
|
|
fgSetBool("/instrumentation/av400w/to-flag", true);
|
|
fgSetBool("/instrumentation/av400w/from-flag", false);
|
|
//printf( "set to-flag\n" );
|
|
} else if ( ind == "F" ) {
|
|
fgSetBool("/instrumentation/av400w/to-flag", false);
|
|
fgSetBool("/instrumentation/av400w/from-flag", true);
|
|
//printf( "set from flag\n" );
|
|
} else {
|
|
fgSetBool("/instrumentation/av400w/to-flag", false);
|
|
fgSetBool("/instrumentation/av400w/from-flag", false);
|
|
//printf( "set t/f both false\n" );
|
|
}
|
|
//printf( "k, %s\n", ind.c_str() );
|
|
}
|
|
else if ( ident == "S" ) {
|
|
string ind = msg.substr(1,5);
|
|
//printf( "S - %s\n", ind.c_str() );
|
|
}
|
|
else {
|
|
// SG_LOG( SG_IO, SG_ALERT, "unknown AV400Sim message = " << msg );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// open hailing frequencies
|
|
bool FGAV400WSimA::open() {
|
|
if ( is_enabled() ) {
|
|
SG_LOG( SG_IO, SG_ALERT, "This shouldn't happen, but the channel "
|
|
<< "is already in use, ignoring" );
|
|
return false;
|
|
}
|
|
|
|
SGIOChannel *io = get_io_channel();
|
|
|
|
if ( ! io->open( get_direction() ) ) {
|
|
SG_LOG( SG_IO, SG_ALERT, "Error opening channel communication layer." );
|
|
return false;
|
|
}
|
|
|
|
set_enabled( true );
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// process work for this port
|
|
bool FGAV400WSimA::process() {
|
|
SGIOChannel *io = get_io_channel();
|
|
|
|
// until we have parsers/generators for the reverse direction,
|
|
// this is hardwired to expect that the physical GPS is slaving
|
|
// from FlightGear.
|
|
|
|
// Send FlightGear data to the external device
|
|
gen_message();
|
|
if ( ! io->write( buf, length ) ) {
|
|
SG_LOG( SG_IO, SG_WARN, "Error writing data." );
|
|
return false;
|
|
}
|
|
|
|
// read the device messages back
|
|
while ( (length = io->readline( buf, FG_MAX_MSG_SIZE )) > 0 ) {
|
|
// SG_LOG( SG_IO, SG_ALERT, "Success reading data." );
|
|
if ( parse_message() ) {
|
|
// SG_LOG( SG_IO, SG_ALERT, "Success parsing data." );
|
|
} else {
|
|
// SG_LOG( SG_IO, SG_ALERT, "Error parsing data." );
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// close the channel
|
|
bool FGAV400WSimA::close() {
|
|
SGIOChannel *io = get_io_channel();
|
|
|
|
set_enabled( false );
|
|
|
|
if ( ! io->close() ) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Start of FGAV400WSimB class methods
|
|
FGAV400WSimB::FGAV400WSimB() :
|
|
hz2(0.0),
|
|
hz2count(0),
|
|
hz2cycles(0),
|
|
flight_phase(0xFF),
|
|
req_hostid(true),
|
|
req_sbas(false)
|
|
{
|
|
hal.clear();
|
|
val.clear();
|
|
hal.append( "\0\0", 2 );
|
|
val.append( "\0\0", 2 );
|
|
outputctr = 0;
|
|
sbas_sel.append( "\0\x01", 2 );
|
|
fdm = new FlightProperties;
|
|
}
|
|
|
|
FGAV400WSimB::~FGAV400WSimB() {
|
|
delete fdm;
|
|
}
|
|
|
|
|
|
bool FGAV400WSimB::gen_hostid_message() {
|
|
char chksum = 0;
|
|
string data = "Cj\r\n";
|
|
data += "COPYRIGHT 2008 GARMIN LTD. \r\n";
|
|
data += "SFTW P/N # 006-B0339-0A\r\n";
|
|
data += "SOFTWARE VER # 3\r\n";
|
|
data += "SOFTWARE REV # 2\r\n";
|
|
data += "SOFTWARE DATE 11/03/2008\r\n";
|
|
data += "SW CRC 8F5E7DD1 AE5D4563\r\n";
|
|
data += "HDWR P/N # 012-00857-01 \r\n";
|
|
data += "SERIAL # 085701214976140\r\n";
|
|
data += "MANUFACTUR DATE 02/26/2007\r\n";
|
|
data += "OPTIONS LIST iiiiiiiiii";
|
|
|
|
// calculate the checksum
|
|
for ( string::const_iterator cli = data.begin(); cli != data.end(); cli++ ) {
|
|
chksum ^= *cli;
|
|
}
|
|
|
|
string sentence( "@@" );
|
|
sentence += data;
|
|
sentence.push_back( chksum );
|
|
sentence += "\x0D\n";
|
|
|
|
length = sentence.length();
|
|
char *bufptr = buf;
|
|
for ( string::const_iterator cli = sentence.begin(); cli != sentence.end(); cli++ ) {
|
|
*bufptr++ = *cli;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FGAV400WSimB::gen_sbas_message() {
|
|
char chksum = 0;
|
|
string data = "WA";
|
|
data.push_back( '\0' );
|
|
data += sbas_sel;
|
|
|
|
// calculate the checksum
|
|
for ( string::const_iterator cli = data.begin(); cli != data.end(); cli++ ) {
|
|
chksum ^= *cli;
|
|
}
|
|
|
|
string sentence( "@@" );
|
|
sentence += data;
|
|
sentence.push_back( chksum );
|
|
sentence += "\x0D\n";
|
|
|
|
length = sentence.length();
|
|
char *bufptr = buf;
|
|
for ( string::const_iterator cli = sentence.begin(); cli != sentence.end(); cli++ ) {
|
|
*bufptr++ = *cli;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Wh - Visible SBAS Satellites (hz2)
|
|
bool FGAV400WSimB::gen_Wh_message() {
|
|
char chksum = 0;
|
|
|
|
// generate the Wh message
|
|
string data = "Wh";
|
|
data.push_back( '\x0F' );
|
|
data.append( "\x3f\x00\x00\x20\x00\x20", 6 );
|
|
data.append( "\x4f\x00\x00\x28\x00\x30", 6 );
|
|
data.append( "\x2d\x00\x00\x48\x01\x05", 6 );
|
|
data.append( "\x1d\x00\x00\x10\x01\x10", 6 );
|
|
data.append( "\x50\x00\x00\x33\x00\x50", 6 );
|
|
data.append( "\x22\x00\x00\x16\x00\x90", 6 );
|
|
data.append( "\x40\x00\x00\x20\x00\x20", 6 );
|
|
data.append( "\x50\x00\x00\x28\x00\x30", 6 );
|
|
data.append( "\x2e\x00\x00\x48\x01\x05", 6 );
|
|
data.append( "\x1e\x00\x00\x10\x01\x10", 6 );
|
|
data.append( "\x51\x00\x00\x33\x00\x50", 6 );
|
|
data.append( "\x23\x00\x00\x16\x00\x90", 6 );
|
|
data.append( "\x1f\x00\x00\x10\x01\x10", 6 );
|
|
data.append( "\x52\x00\x00\x33\x00\x50", 6 );
|
|
data.append( "\x24\x00\x00\x16\x00\x90", 6 );
|
|
data.push_back( '0' );
|
|
|
|
// calculate the checksum
|
|
for ( string::const_iterator cli = data.begin(); cli != data.end(); cli++ ) {
|
|
chksum ^= *cli;
|
|
}
|
|
|
|
string sentence( "@@" );
|
|
sentence += data;
|
|
sentence.push_back( chksum );
|
|
sentence += "\x0D\n";
|
|
|
|
length = sentence.length();
|
|
char *bufptr = buf;
|
|
for ( string::const_iterator cli = sentence.begin(); cli != sentence.end(); cli++ ) {
|
|
*bufptr++ = *cli;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// Wx - Channel Status Message (hz2)
|
|
bool FGAV400WSimB::gen_Wx_message() {
|
|
char chksum = 0;
|
|
|
|
// Now process the Wx message
|
|
string data = "Wx";
|
|
data.push_back( (char)( fgGetInt( "/sim/time/utc/month") & 0xFF ) );
|
|
data.push_back( (char)( fgGetInt( "/sim/time/utc/day") & 0xFF ) );
|
|
data.push_back( (char)( (fgGetInt( "/sim/time/utc/year") >> 8 ) & 0xFF ) );
|
|
data.push_back( (char)( fgGetInt( "/sim/time/utc/year") & 0xFF ) );
|
|
data.push_back( (char)( fgGetInt( "/sim/time/utc/hour") & 0xFF ) );
|
|
data.push_back( (char)( fgGetInt( "/sim/time/utc/minute") & 0xFF ) );
|
|
data.push_back( (char)( fgGetInt( "/sim/time/utc/second") & 0xFF ) );
|
|
data.append( "\x00\x00\x00\x00", 4 );
|
|
|
|
for ( int xctr = 0; xctr < 15; xctr++ ) {
|
|
data.append( "\x00\x00\x00\x00\x00\x00\x00\x00", 8 );
|
|
}
|
|
data.push_back( '\0' );
|
|
|
|
// calculate the checksum
|
|
for ( string::const_iterator cli = data.begin(); cli != data.end(); cli++ ) {
|
|
chksum ^= *cli;
|
|
}
|
|
|
|
string sentence( "@@" );
|
|
sentence += data;
|
|
sentence.push_back( chksum );
|
|
sentence += "\x0D\n";
|
|
|
|
// cout << sentence;
|
|
length = sentence.length();
|
|
char *bufptr = buf;
|
|
for ( string::const_iterator cli = sentence.begin(); cli != sentence.end(); cli++ ) {
|
|
*bufptr++ = *cli;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// Wt - Position and Navigation status
|
|
bool FGAV400WSimB::gen_Wt_message() {
|
|
char chksum = 0;
|
|
|
|
// generate the Wt message
|
|
string data = "Wt";
|
|
data.push_back( (char)( fgGetInt( "/sim/time/utc/month") & 0xFF ) );
|
|
data.push_back( (char)( fgGetInt( "/sim/time/utc/day") & 0xFF ) );
|
|
data.push_back( (char)( (fgGetInt( "/sim/time/utc/year") >> 8 ) & 0xFF ) );
|
|
data.push_back( (char)( fgGetInt( "/sim/time/utc/year") & 0xFF ) );
|
|
data.push_back( (char)( fgGetInt( "/sim/time/utc/hour") & 0xFF ) );
|
|
data.push_back( (char)( fgGetInt( "/sim/time/utc/minute") & 0xFF ) );
|
|
data.push_back( (char)( fgGetInt( "/sim/time/utc/second") & 0xFF ) );
|
|
data.append( "\x00\x00\x00\x00", 4 );
|
|
|
|
// get latitude in milliarcseconds
|
|
double latd = fdm->get_Latitude() * SGD_RADIANS_TO_DEGREES;
|
|
latd *= DEG_TO_MILLIARCSECS;
|
|
int latitude = (int)latd;
|
|
data.push_back( (char)( ( latitude >> 24 ) & 0xFF ) );
|
|
data.push_back( (char)( ( latitude >> 16 ) & 0xFF ) );
|
|
data.push_back( (char)( ( latitude >> 8 ) & 0xFF ) );
|
|
data.push_back( (char)( latitude & 0xFF ) );
|
|
|
|
// get longitude in milliarcseconds
|
|
double lond = fdm->get_Longitude() * SGD_RADIANS_TO_DEGREES;
|
|
lond *= DEG_TO_MILLIARCSECS;
|
|
int longitude = (int)lond;
|
|
data.push_back( (char)( ( longitude >> 24 ) & 0xFF ) );
|
|
data.push_back( (char)( ( longitude >> 16 ) & 0xFF ) );
|
|
data.push_back( (char)( ( longitude >> 8 ) & 0xFF ) );
|
|
data.push_back( (char)( longitude & 0xFF ) );
|
|
|
|
|
|
// Altitude settings
|
|
double alt = fdm->get_Altitude();
|
|
if ( alt > 99999.0 ) { alt = 99999.0; }
|
|
|
|
// send the WGS-84 ellipsoid height om /-1, (just use regular altitude)
|
|
alt *= SG_FEET_TO_METER;
|
|
int altm = (int)( alt * 100.0f );
|
|
data.push_back( (char)( ( altm >> 24 ) & 0xFF ) );
|
|
data.push_back( (char)( ( altm >> 16 ) & 0xFF ) );
|
|
data.push_back( (char)( ( altm >> 8 ) & 0xFF ) );
|
|
data.push_back( (char)( altm & 0xFF ) );
|
|
|
|
// put in the geoid height in 0.1 meters
|
|
data.push_back( (char)( ( altm >> 24 ) & 0xFF ) );
|
|
data.push_back( (char)( ( altm >> 16 ) & 0xFF ) );
|
|
data.push_back( (char)( ( altm >> 8 ) & 0xFF ) );
|
|
data.push_back( (char)( altm & 0xFF ) );
|
|
|
|
// get ground speed
|
|
double gskt = fgGetDouble( "/velocities/groundspeed-kt" );
|
|
gskt *= SG_KT_TO_MPS;
|
|
int gsm = (int)( gskt * 100.0f );
|
|
data.push_back( (char)( ( gsm >> 8 ) & 0xFF ) );
|
|
data.push_back( (char)( gsm & 0xFF ) );
|
|
|
|
// ground track
|
|
double trkdeg = fgGetDouble("/orientation/heading-deg");
|
|
int hdg = (int)(trkdeg * 10.0f);
|
|
data.push_back( (char)( ( hdg >> 8 ) & 0xFF ) );
|
|
data.push_back( (char)( hdg & 0xFF ) );
|
|
|
|
// vertical velocity
|
|
double climb_fpm = fgGetDouble( "/velocities/vertical-speed-fps" );
|
|
climb_fpm *= SG_FEET_TO_METER;
|
|
int vvm = (int)( climb_fpm * 50.0f );
|
|
data.push_back( (char)( ( vvm >> 8 ) & 0xFF ) );
|
|
data.push_back( (char)( vvm & 0xFF ) );
|
|
|
|
// navigation solution status
|
|
data.push_back( '\0' );
|
|
|
|
// HFOM/VFOM
|
|
data.append( "\0\x09\0\x09", 4 );
|
|
|
|
// ARINC 748 Mode
|
|
data.push_back( '\x0D' );
|
|
|
|
// Channel Tracking
|
|
data += "\x7F\xFF";
|
|
|
|
// calculate the checksum
|
|
for ( string::const_iterator cli = data.begin(); cli != data.end(); cli++ ) {
|
|
chksum ^= *cli;
|
|
}
|
|
|
|
string sentence( "@@" );
|
|
sentence += data;
|
|
sentence.push_back( chksum );
|
|
sentence += "\x0D\n";
|
|
|
|
length = sentence.length();
|
|
char *bufptr = buf;
|
|
for ( string::const_iterator cli = sentence.begin(); cli != sentence.end(); cli++ ) {
|
|
*bufptr++ = *cli;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// Wm - Data integrity status
|
|
bool FGAV400WSimB::gen_Wm_message() {
|
|
char chksum = 0;
|
|
|
|
// generate the Wt message
|
|
string data = "Wm";
|
|
|
|
// flight phase
|
|
data.push_back( flight_phase );
|
|
|
|
// HAL and VAL
|
|
if ( hal.empty() ) {
|
|
data.append( "\0\0", 2 );
|
|
}
|
|
else {
|
|
data += hal;
|
|
}
|
|
|
|
if ( val.empty() ) {
|
|
data.append( "\0\0", 2 );
|
|
}
|
|
else {
|
|
data += val;
|
|
}
|
|
|
|
// Integrity status
|
|
data.append( "\x00\x00\x00", 3 );
|
|
data.append( "\x00\x01\x00\x01\x00\x01\x00\x01", 8 );
|
|
data.append( "\x00\x0F\x00\x0F\x00\x0F", 6 );
|
|
|
|
// calculate the checksum
|
|
for ( string::const_iterator cli = data.begin(); cli != data.end(); cli++ ) {
|
|
chksum ^= *cli;
|
|
}
|
|
|
|
string sentence( "@@" );
|
|
sentence += data;
|
|
sentence.push_back( chksum );
|
|
sentence += "\x0D\n";
|
|
|
|
length = sentence.length();
|
|
char *bufptr = buf;
|
|
for ( string::const_iterator cli = sentence.begin(); cli != sentence.end(); cli++ ) {
|
|
*bufptr++ = *cli;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Wv - 3d velocity
|
|
bool FGAV400WSimB::gen_Wv_message() {
|
|
char chksum = 0;
|
|
|
|
// generate the Wt message
|
|
string data = "Wv";
|
|
|
|
// data is valid
|
|
data += "1";
|
|
|
|
// N velocity in .01 m/s
|
|
double vn_mps = fgGetDouble( "/velocities/speed-north-fps" ) * SG_FEET_TO_METER;
|
|
int vnm = (int)( vn_mps * 100 );
|
|
data.push_back( (char)( ( vnm >> 24 ) & 0xFF ) );
|
|
data.push_back( (char)( ( vnm >> 16 ) & 0xFF ) );
|
|
data.push_back( (char)( ( vnm >> 8 ) & 0xFF ) );
|
|
data.push_back( (char)( vnm & 0xFF ) );
|
|
|
|
// E velocity in .01 m/s
|
|
double ve_mps = fgGetDouble( "/velocities/speed-east-fps" ) * SG_FEET_TO_METER;
|
|
int vne = (int)( ve_mps * 100 );
|
|
data.push_back( (char)( ( vne >> 24 ) & 0xFF ) );
|
|
data.push_back( (char)( ( vne >> 16 ) & 0xFF ) );
|
|
data.push_back( (char)( ( vne >> 8 ) & 0xFF ) );
|
|
data.push_back( (char)( vne & 0xFF ) );
|
|
|
|
// Up velocity in .01 m/s
|
|
double climb_mps = fgGetDouble( "/velocities/vertical-speed-fps" ) * SG_FEET_TO_METER;
|
|
int vnup = (int)( climb_mps * 100 );
|
|
data.push_back( (char)( ( vnup >> 24 ) & 0xFF ) );
|
|
data.push_back( (char)( ( vnup >> 16 ) & 0xFF ) );
|
|
data.push_back( (char)( ( vnup >> 8 ) & 0xFF ) );
|
|
data.push_back( (char)( vnup & 0xFF ) );
|
|
|
|
// calculate the checksum
|
|
for ( string::const_iterator cli = data.begin(); cli != data.end(); cli++ ) {
|
|
chksum ^= *cli;
|
|
}
|
|
|
|
string sentence( "@@" );
|
|
sentence += data;
|
|
sentence.push_back( chksum );
|
|
sentence += "\x0D\n";
|
|
|
|
// cout << sentence;
|
|
length = sentence.length();
|
|
char *bufptr = buf;
|
|
for ( string::const_iterator cli = sentence.begin(); cli != sentence.end(); cli++ ) {
|
|
*bufptr++ = *cli;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool FGAV400WSimB::verify_checksum( string message, int datachars ) {
|
|
bool bRet = false;
|
|
string dataseg = message.substr(SOM_SIZE, datachars);
|
|
char chksum = 0;
|
|
char cs = message[SOM_SIZE + datachars];
|
|
for ( string::const_iterator cli = dataseg.begin();
|
|
cli != dataseg.end(); cli++ ) {
|
|
chksum ^= *cli;
|
|
}
|
|
|
|
if ( chksum == cs ) {
|
|
bRet = true;
|
|
}
|
|
else {
|
|
SG_LOG( SG_IO, SG_INFO, "bad input checksum: " << message );
|
|
//string msgid = asciitize_message( message );
|
|
//printf( "FGAV400SimB::verify_checksum bad input checksum:\n%s\n", msgid.c_str() );
|
|
}
|
|
|
|
return( bRet );
|
|
}
|
|
|
|
|
|
string FGAV400WSimB::asciitize_message( string message ) {
|
|
string asciimsg;
|
|
|
|
for ( string::const_iterator cli = message.begin();
|
|
cli != message.end(); cli++ )
|
|
{
|
|
unsigned char uc = static_cast<unsigned char>(*cli);
|
|
if ( uc >= 32 && uc <= 127 ) {
|
|
asciimsg += *cli;
|
|
}
|
|
else {
|
|
char tempbuf[20];
|
|
sprintf( tempbuf, "\\x%02X", uc );
|
|
asciimsg += tempbuf;
|
|
}
|
|
}
|
|
|
|
return( asciimsg );
|
|
}
|
|
|
|
string FGAV400WSimB::buffer_to_string() {
|
|
string message;
|
|
char *bufctr = buf;
|
|
|
|
for ( int xctr = 0; xctr < length; xctr++ ) {
|
|
message.push_back( *bufctr++ );
|
|
}
|
|
return( message );
|
|
}
|
|
|
|
|
|
// parse AV400Sim message
|
|
bool FGAV400WSimB::parse_message() {
|
|
SG_LOG( SG_IO, SG_INFO, "parse AV400WSimB message" );
|
|
|
|
string msg = buffer_to_string();
|
|
|
|
string som = msg.substr(0, 2);
|
|
if ( som != "@@" ) {
|
|
SG_LOG( SG_IO, SG_INFO, "bad start message" );
|
|
return false;
|
|
}
|
|
|
|
string ident = msg.substr(2,2);
|
|
|
|
if ( ident == "AH" ) { // Flight Phase
|
|
if ( verify_checksum( msg, 3 ) ) {
|
|
flight_phase = msg[4];
|
|
//string ascmsg = asciitize_message( msg );
|
|
//printf( "%10d received AH %s\n", outputctr, ascmsg.c_str() );
|
|
switch( flight_phase ) {
|
|
case FGAV400WSimB::PHASE_OCEANIC: // Oceanic
|
|
if ( hal.empty() ) {
|
|
hal = "\x39\xE0";
|
|
}
|
|
if ( val.empty() ) {
|
|
val = "\x00\x00";
|
|
}
|
|
fgSetBool( "/instrumentation/av400w/has-gs", false );
|
|
break;
|
|
|
|
case PHASE_ENROUTE: // Enroute
|
|
if ( hal.empty() ) {
|
|
hal = "\x1C\xF0";
|
|
}
|
|
if ( val.empty() ) {
|
|
val = "\x00\x00";
|
|
}
|
|
fgSetBool( "/instrumentation/av400w/has-gs", false );
|
|
break;
|
|
|
|
case PHASE_TERM: // Terminal
|
|
if ( hal.empty() ) {
|
|
hal = "\x0E\x78";
|
|
}
|
|
if ( val.empty() ) {
|
|
val = "\x00\x00";
|
|
}
|
|
fgSetBool( "/instrumentation/av400w/has-gs", false );
|
|
break;
|
|
|
|
case PHASE_NONPREC: // Non Precision Approach
|
|
if ( hal.empty() ) {
|
|
hal = "\x04\x57";
|
|
}
|
|
if ( val.empty() ) {
|
|
val = "\x00\x00";
|
|
}
|
|
fgSetBool( "/instrumentation/av400w/has-gs", false );
|
|
break;
|
|
|
|
case PHASE_LNAVVNAV: // LNAV/VNAV
|
|
if ( hal.empty() ) {
|
|
hal = "\x04\x57";
|
|
}
|
|
if ( val.empty() ) {
|
|
val = "\x00\x64";
|
|
}
|
|
fgSetBool( "/instrumentation/av400w/has-gs", true );
|
|
break;
|
|
|
|
case PHASE_LPVLP: // LPV/LP
|
|
if ( hal.empty() ) {
|
|
hal = "\x00\x00";
|
|
}
|
|
if ( val.empty() ) {
|
|
val = "\x00\x00";
|
|
}
|
|
fgSetBool( "/instrumentation/av400w/has-gs", true );
|
|
break;
|
|
|
|
default:
|
|
if ( hal.empty() ) {
|
|
hal = "\x00\x00";
|
|
}
|
|
if ( val.empty() ) {
|
|
val = "\x00\x00";
|
|
}
|
|
fgSetBool( "/instrumentation/av400w/has-gs", false );
|
|
break;
|
|
}
|
|
//printf( "AH flight status: %c\n", flight_phase + '0' );
|
|
}
|
|
}
|
|
else if ( ident == "AI" ) { // HAL
|
|
if ( verify_checksum( msg, 4 ) ) {
|
|
hal = msg.substr(4,2);
|
|
//printf( "%10d received AI\n", outputctr );
|
|
}
|
|
}
|
|
else if ( ident == "Cj" ) { // Host ID
|
|
if ( verify_checksum( msg, 2 ) ) {
|
|
req_hostid = true;
|
|
//printf( "%10d received Cj\n", outputctr );
|
|
}
|
|
}
|
|
else if ( ident == "WA" ) { // SBAS selection
|
|
if ( verify_checksum( msg, 5 ) ) {
|
|
sbas_sel = msg.substr( 5, 2 );
|
|
req_sbas = true;
|
|
//printf( "%10d received WA\n", outputctr );
|
|
}
|
|
}
|
|
else if ( ident == "Wd" ) { // VAL
|
|
if ( verify_checksum( msg, 4 ) ) {
|
|
val = msg.substr( 4, 2 );
|
|
//printf( "%10d received Wd\n", outputctr );
|
|
}
|
|
}
|
|
else if ( ident == "WY" ) { // ???? Not listed in protocol document
|
|
// Do nothing until we know what it does
|
|
}
|
|
else {
|
|
string unkmsg = msg.substr( 0, 4 );
|
|
printf( "parse_message unknown: %s\n", unkmsg.c_str() );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// open hailing frequencies
|
|
bool FGAV400WSimB::open() {
|
|
if ( is_enabled() ) {
|
|
SG_LOG( SG_IO, SG_ALERT, "This shouldn't happen, but the channel "
|
|
<< "is already in use, ignoring" );
|
|
return false;
|
|
}
|
|
|
|
SGIOChannel *io = get_io_channel();
|
|
|
|
if ( ! io->open( get_direction() ) ) {
|
|
SG_LOG( SG_IO, SG_ALERT, "Error opening channel communication layer." );
|
|
return false;
|
|
}
|
|
|
|
set_enabled( true );
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// process work for this port
|
|
bool FGAV400WSimB::process() {
|
|
SGIOChannel *io = get_io_channel();
|
|
|
|
// read the device messages back
|
|
// Because the protocol allows for binary data, we can't just read
|
|
// ascii lines.
|
|
char readbuf[10];
|
|
char *bufptr = buf;
|
|
int templen;
|
|
bool gotCr = false;
|
|
bool gotLf = false;
|
|
bool som1 = false;
|
|
bool som2 = false;
|
|
length = 0;
|
|
|
|
while ( ( templen = io->read( readbuf, 1 ) ) == 1 ) {
|
|
if ( !som1 && !som2 ) {
|
|
if ( *readbuf == '@' ) {
|
|
som1 = true;
|
|
}
|
|
else {
|
|
continue;
|
|
}
|
|
}
|
|
else if ( !som2 ) {
|
|
if ( *readbuf == '@' ) {
|
|
som2 = true;
|
|
}
|
|
else {
|
|
som1 = false;
|
|
continue;
|
|
}
|
|
}
|
|
else if ( som1 && som2 ) {
|
|
if ( *readbuf == '\n' && !gotCr ) { // check for a carriage return
|
|
gotCr = true;
|
|
}
|
|
else if ( *readbuf == '\n' && gotCr ) { // see if we got a cr/lf
|
|
gotLf = true;
|
|
}
|
|
else if ( gotCr ) { // we had a cr but the next char was not a lf, so just must be data
|
|
gotCr = false;
|
|
}
|
|
}
|
|
|
|
*bufptr++ = *readbuf;
|
|
length++;
|
|
|
|
if ( gotCr && gotLf ) { // message done
|
|
if ( parse_message() ) {
|
|
// SG_LOG( SG_IO, SG_ALERT, "Success parsing data." );
|
|
} else {
|
|
// SG_LOG( SG_IO, SG_ALERT, "Error parsing data." );
|
|
}
|
|
length = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
// Check for polled messages
|
|
if ( req_hostid ) {
|
|
gen_hostid_message();
|
|
if ( ! io->write( buf, length ) ) {
|
|
SG_LOG( SG_IO, SG_WARN, "Error writing data." );
|
|
printf( "Error sending HostID\n" );
|
|
return false;
|
|
}
|
|
//printf( "Sent HostID, %d bytes\n", length );
|
|
req_hostid = false;
|
|
}
|
|
else if ( req_sbas ) {
|
|
gen_sbas_message();
|
|
if ( ! io->write( buf, length ) ) {
|
|
SG_LOG( SG_IO, SG_WARN, "Error writing data." );
|
|
printf( "Error sending SBAS\n" );
|
|
return false;
|
|
}
|
|
//printf( "Sent SBAS, %d bytes\n", length );
|
|
req_sbas = false;
|
|
}
|
|
|
|
// Send the 5Hz messages
|
|
gen_Wt_message();
|
|
if ( ! io->write( buf, length ) ) {
|
|
SG_LOG( SG_IO, SG_WARN, "Error writing data." );
|
|
printf( "Error writing hz message\n" );
|
|
return false;
|
|
}
|
|
//printf( "Sent Wt, %d bytes\n", length );
|
|
|
|
gen_Wm_message();
|
|
if ( ! io->write( buf, length ) ) {
|
|
SG_LOG( SG_IO, SG_WARN, "Error writing data." );
|
|
printf( "Error writing hz message\n" );
|
|
return false;
|
|
}
|
|
//printf( "Sent Wm, %d bytes\n", length );
|
|
|
|
gen_Wv_message();
|
|
if ( ! io->write( buf, length ) ) {
|
|
SG_LOG( SG_IO, SG_WARN, "Error writing data." );
|
|
printf( "Error writing hz message\n" );
|
|
return false;
|
|
}
|
|
//printf( "Sent Wv, %d bytes\n", length );
|
|
|
|
hz2count++;
|
|
if ( hz2 > 0 && ( hz2count % hz2cycles == 0 ) ) {
|
|
// Send the 1Hz messages
|
|
gen_Wh_message();
|
|
if ( ! io->write( buf, length ) ) {
|
|
SG_LOG( SG_IO, SG_WARN, "Error writing data." );
|
|
printf( "Error writing hz2 message\n" );
|
|
return false;
|
|
}
|
|
//printf( "Sent Wh, %d bytes\n", length );
|
|
|
|
gen_Wx_message();
|
|
if ( ! io->write( buf, length ) ) {
|
|
SG_LOG( SG_IO, SG_WARN, "Error writing data." );
|
|
printf( "Error writing hz2 message\n" );
|
|
return false;
|
|
}
|
|
//printf( "Sent Wx, %d bytes\n", length );
|
|
}
|
|
|
|
// read the device messages back again to make sure we don't miss anything
|
|
bufptr = buf;
|
|
templen = 0;
|
|
gotCr = false;
|
|
gotLf = false;
|
|
som1 = false;
|
|
som2 = false;
|
|
length = 0;
|
|
|
|
while ( ( templen = io->read( readbuf, 1 ) ) == 1 ) {
|
|
if ( !som1 && !som2 ) {
|
|
if ( *readbuf == '@' ) {
|
|
som1 = true;
|
|
}
|
|
else {
|
|
continue;
|
|
}
|
|
}
|
|
else if ( !som2 ) {
|
|
if ( *readbuf == '@' ) {
|
|
som2 = true;
|
|
}
|
|
else {
|
|
som1 = false;
|
|
continue;
|
|
}
|
|
}
|
|
else if ( som1 && som2 ) {
|
|
if ( *readbuf == '\n' && !gotCr ) { // check for a carriage return
|
|
gotCr = true;
|
|
}
|
|
else if ( *readbuf == '\n' && gotCr ) { // see if we got a cr/lf
|
|
gotLf = true;
|
|
}
|
|
else if ( gotCr ) { // we had a cr but the next char was not a lf, so just must be data
|
|
gotCr = false;
|
|
}
|
|
}
|
|
|
|
*bufptr++ = *readbuf;
|
|
length++;
|
|
|
|
if ( gotCr && gotLf ) { // message done
|
|
//string msg = buffer_to_string();
|
|
//string ascmsg = asciitize_message( msg );
|
|
//printf( "Received message\n" );
|
|
//printf( "%s\n", ascmsg.c_str() );
|
|
//printf( "got message\n" );
|
|
if ( parse_message() ) {
|
|
// SG_LOG( SG_IO, SG_ALERT, "Success parsing data." );
|
|
} else {
|
|
// SG_LOG( SG_IO, SG_ALERT, "Error parsing data." );
|
|
}
|
|
length = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
outputctr++;
|
|
if ( outputctr % 10 == 0 ) {
|
|
//printf( "AV400WSimB::process finished\n" );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// close the channel
|
|
bool FGAV400WSimB::close() {
|
|
SGIOChannel *io = get_io_channel();
|
|
|
|
set_enabled( false );
|
|
|
|
if ( ! io->close() ) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|