first commit

This commit is contained in:
Your Name
2022-10-20 20:29:11 +08:00
commit 4d531f8044
3238 changed files with 1387862 additions and 0 deletions

1019
src/Network/ATC-Inputs.cxx Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,92 @@
// ATC-Inputs.hxx -- Translate ATC hardware inputs to FGFS properties
//
// Written by Curtis Olson, started November 2004.
//
// Copyright (C) 2004 Curtis L. Olson - http://www.flightgear.org/~curt
//
// 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$
#ifndef _FG_ATC_INPUTS_HXX
#define _FG_ATC_INPUTS_HXX
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <Main/fg_props.hxx>
#define ATC_ANAL_IN_VALUES 32
#define ATC_ANAL_IN_BYTES (2 * ATC_ANAL_IN_VALUES)
#define ATC_RADIO_SWITCH_BYTES 32
#define ATC_SWITCH_BYTES 16
#define ATC_NUM_COLS 8
class FGATCInput {
int is_open;
int board;
SGPath config;
int analog_in_fd;
int radios_fd;
int switches_fd;
char analog_in_file[256];
char radios_file[256];
char switches_file[256];
unsigned char analog_in_bytes[ATC_ANAL_IN_BYTES];
int analog_in_data[ATC_ANAL_IN_VALUES];
unsigned char radio_switch_data[ATC_RADIO_SWITCH_BYTES];
unsigned char switch_data[ATC_SWITCH_BYTES];
SGPropertyNode_ptr ignore_flight_controls;
SGPropertyNode_ptr ignore_pedal_controls;
SGPropertyNode_ptr analog_in_node;
SGPropertyNode_ptr radio_in_node;
SGPropertyNode_ptr switches_node;
void init_config();
bool do_analog_in();
bool do_radio_switches();
bool do_switches();
public:
// Constructor: The _board parameter specifies which board to
// reference. Possible values are 0 or 1. The _config_file
// parameter specifies the location of the input config file (xml)
FGATCInput( const int _board, const SGPath &_config_file );
// Destructor
~FGATCInput() { }
bool open();
// process the hardware inputs. This code assumes the calling
// layer will lock the hardware.
bool process();
bool close();
};
#endif // _FG_ATC_INPUTS_HXX

284
src/Network/ATC-Main.cxx Normal file
View File

@@ -0,0 +1,284 @@
// ATC-Main.cxx -- FGFS interface to ATC hardware
//
// Written by Curtis Olson, started January 2002
//
// Copyright (C) 2002 Curtis L. Olson - http://www.flightgear.org/~curt
//
// 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 <simgear/compiler.h>
#include <stdlib.h> // atoi() atof() abs()
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h> //snprintf
#ifdef _WIN32
# include <io.h> //lseek, read, write
#endif
#include <string>
#include <simgear/debug/logstream.hxx>
#include <simgear/props/props_io.hxx>
#include <simgear/io/iochannel.hxx>
#include <simgear/math/sg_types.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/props/props.hxx>
#include <simgear/structure/exception.hxx>
#include <Scripting/NasalSys.hxx>
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
#include "ATC-Main.hxx"
using std::string;
using std::cout;
using std::endl;
using std::vector;
// Lock the ATC hardware
static int fgATCMainLock( int fd ) {
// rewind
lseek( fd, 0, SEEK_SET );
char tmp[2];
int result = read( fd, tmp, 1 );
if ( result != 1 ) {
SG_LOG( SG_IO, SG_DEBUG, "Lock failed" );
}
return result;
}
// Write a radios command
static int fgATCMainRelease( int fd ) {
// rewind
lseek( fd, 0, SEEK_SET );
char tmp[2];
tmp[0] = tmp[1] = 0;
int result = write( fd, tmp, 1 );
if ( result != 1 ) {
SG_LOG( SG_IO, SG_DEBUG, "Release failed" );
}
return result;
}
void FGATCMain::init_config() {
#if defined( unix ) || defined( __CYGWIN__ )
// Next check home directory for .fgfsrc.hostname file
SGPath atcsim_config = SGPath::home();
atcsim_config.append( ".fgfs-atc610x.xml" );
try {
SG_LOG(SG_NETWORK, SG_ALERT,
"Warning: loading deprecated config file: " << atcsim_config);
readProperties( atcsim_config, globals->get_props() );
} catch (const sg_exception &e) {
// fail silently, this is an old style config file I want to continue
// to support if it exists.
}
#endif
}
// Open and initialize ATC hardware
bool FGATCMain::open() {
if ( is_enabled() ) {
SG_LOG( SG_IO, SG_ALERT, "This shouldn't happen, but the channel "
<< "is already in use, ignoring" );
return false;
}
SG_LOG( SG_IO, SG_ALERT,
"Initializing ATC hardware, please wait ..." );
// This loads the config parameters generated by "simcal"
init_config();
/////////////////////////////////////////////////////////////////////
// Open the /proc files
/////////////////////////////////////////////////////////////////////
string lock0_file = "/proc/atcflightsim/board0/lock";
string lock1_file = "/proc/atcflightsim/board1/lock";
lock0_fd = ::open( lock0_file.c_str(), O_RDWR );
if ( lock0_fd == -1 ) {
SG_LOG( SG_IO, SG_ALERT, "errno = " << errno );
char msg[256];
snprintf( msg, 256, "Error opening %s", lock0_file.c_str() );
perror( msg );
exit( -1 );
}
lock1_fd = ::open( lock1_file.c_str(), O_RDWR );
if ( lock1_fd == -1 ) {
SG_LOG( SG_IO, SG_ALERT, "errno = " << errno );
char msg[256];
snprintf( msg, 256, "Error opening %s", lock1_file.c_str() );
perror( msg );
exit( -1 );
}
if ( !input0_path.isNull() ) {
input0 = new FGATCInput( 0, input0_path );
input0->open();
}
if ( !input1_path.isNull() ) {
input1 = new FGATCInput( 1, input1_path );
input1->open();
}
if ( !output0_path.isNull() ) {
output0 = new FGATCOutput( 0, output0_path );
output0->open( lock0_fd );
}
if ( !output1_path.isNull() ) {
output1 = new FGATCOutput( 1, output1_path );
output1->open( lock1_fd );
}
set_hz( 30 ); // default to processing requests @ 30Hz
set_enabled( true );
/////////////////////////////////////////////////////////////////////
// Finished initing hardware
/////////////////////////////////////////////////////////////////////
SG_LOG( SG_IO, SG_ALERT,
"Done initializing ATC hardware." );
return true;
}
bool FGATCMain::process() {
// cout << "Main::process()\n";
bool board0_locked = false;
bool board1_locked = false;
if ( input0 != NULL || output0 != NULL ) {
// Lock board0 if we have a configuration for it
if ( fgATCMainLock( lock0_fd ) > 0 ) {
board0_locked = true;
}
}
if ( input1 != NULL || output1 != NULL ) {
// Lock board1 if we have a configuration for it
if ( fgATCMainLock( lock1_fd ) > 0 ) {
board1_locked = true;
}
}
// cout << " locks: ";
// if ( board0_locked ) { cout << "board0 "; }
// if ( board1_locked ) { cout << "board1 "; }
// cout << endl;
// process the ATC inputs
if ( input0 != NULL && board0_locked ) {
input0->process();
}
if ( input1 != NULL && board1_locked ) {
input1->process();
}
// run our custom nasal script. This is a layer above the raw
// hardware inputs. It handles situations where there isn't a
// direct 1-1 linear mapping between ATC functionality and FG
// functionality, and handles situations where FG expects more
// functionality from the interface than the ATC hardware can
// directly provide.
FGNasalSys *n = (FGNasalSys*)globals->get_subsystem("nasal");
bool result = n->parseAndRun( "atcsim.update()" );
if ( !result ) {
SG_LOG( SG_NETWORK, SG_ALERT, "Nasal: atcsim.update() failed!" );
}
// process the ATC outputs
if ( output0 != NULL && board0_locked ) {
output0->process();
}
if ( output1 != NULL && board1_locked ) {
output1->process();
}
if ( board0_locked ) {
fgATCMainRelease( lock0_fd );
}
if ( board1_locked ) {
fgATCMainRelease( lock1_fd );
}
return true;
}
bool FGATCMain::close() {
cout << "FGATCMain::close()" << endl;
int result;
if ( input0 != NULL ) {
input0->close();
}
if ( input1 != NULL ) {
input1->close();
}
if ( output0 != NULL ) {
output0->close();
}
if ( output1 != NULL ) {
output1->close();
}
result = ::close( lock0_fd );
if ( result == -1 ) {
SG_LOG( SG_IO, SG_ALERT, "errno = " << errno );
char msg[256];
snprintf( msg, 256, "Error closing lock0_fd" );
perror( msg );
exit( -1 );
}
result = ::close( lock1_fd );
if ( result == -1 ) {
SG_LOG( SG_IO, SG_ALERT, "errno = " << errno );
char msg[256];
snprintf( msg, 256, "Error closing lock1_fd" );
perror( msg );
exit( -1 );
}
return true;
}

96
src/Network/ATC-Main.hxx Normal file
View File

@@ -0,0 +1,96 @@
// ATC-Main.hxx -- FGFS interface to ATC 610x hardware
//
// Written by Curtis Olson, started January 2002.
//
// Copyright (C) 2002 Curtis L. Olson - http://www.flightgear.org/~curt
//
// 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$
#ifndef _FG_ATC_MAIN_HXX
#define _FG_ATC_MAIN_HXX
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <simgear/misc/sg_path.hxx>
#include <Main/fg_props.hxx>
#include "protocol.hxx"
#include "ATC-Inputs.hxx"
#include "ATC-Outputs.hxx"
class FGATCMain : public FGProtocol {
FGATCInput *input0; // board0 input interface class
FGATCInput *input1; // board1 input interface class
FGATCOutput *output0; // board0 output interface class
FGATCOutput *output1; // board1 output interface class
SGPath input0_path;
SGPath input1_path;
SGPath output0_path;
SGPath output1_path;
int lock0_fd;
int lock1_fd;
public:
FGATCMain() :
input0(NULL),
input1(NULL),
output0(NULL),
output1(NULL),
input0_path(""),
input1_path(""),
output0_path(""),
output1_path("")
{ }
~FGATCMain() {
delete input0;
delete input1;
delete output0;
delete output1;
}
// Open and initialize ATC 610x hardware
bool open();
void init_config();
bool process();
bool close();
inline void set_path_names( const SGPath &in0, const SGPath &in1,
const SGPath &out0, const SGPath &out1 )
{
input0_path = in0;
input1_path = in1;
output0_path = out0;
output1_path = out1;
}
};
#endif // _FG_ATC_MAIN_HXX

1106
src/Network/ATC-Outputs.cxx Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,95 @@
// ATC-Outputs.hxx -- Translate FGFS properties into ATC hardware outputs
//
// Written by Curtis Olson, started November 2004.
//
// Copyright (C) 2004 Curtis L. Olson - http://www.flightgear.org/~curt
//
// 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$
#ifndef _FG_ATC_OUTPUTS_HXX
#define _FG_ATC_OUTPUTS_HXX
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <Main/fg_props.hxx>
#define ATC_RADIO_DISPLAY_BYTES 48
#define ATC_ANALOG_OUT_CHANNELS 48
#define ATC_COMPASS_CH 5
#define ATC_STEPPER_HOME 0xC0
class FGATCOutput {
int is_open;
int board;
SGPath config;
int analog_out_fd;
int lamps_fd;
int radio_display_fd;
int stepper_fd;
char analog_out_file[256];
char lamps_file[256];
char radio_display_file[256];
char stepper_file[256];
unsigned char analog_out_data[ATC_ANALOG_OUT_CHANNELS*2];
unsigned char radio_display_data[ATC_RADIO_DISPLAY_BYTES];
SGPropertyNode_ptr analog_out_node;
SGPropertyNode_ptr lamps_out_node;
SGPropertyNode_ptr radio_display_node;
SGPropertyNode_ptr steppers_node;
void init_config();
bool do_analog_out();
bool do_lamps();
bool do_radio_display();
bool do_steppers();
// hardwired stepper motor code
float compass_position;
public:
// Constructor: The _board parameter specifies which board to
// reference. Possible values are 0 or 1. The _config_file
// parameter specifies the location of the output config file (xml)
FGATCOutput( const int _board, const SGPath &_config_file );
// Destructor
~FGATCOutput() { }
// need to pass in atc hardware lock_fd so that the radios and
// lamps can be blanked and so that the compass can be homed.
bool open( int lock_fd );
// process the hardware outputs. This code assumes the calling
// layer will lock the hardware.
bool process();
bool close();
};
#endif // _FG_ATC_OUTPUTS_HXX

425
src/Network/AV400.cxx Normal file
View File

@@ -0,0 +1,425 @@
// AV400.cxx -- Garmin 400 series protocal class
//
// Written by Curtis Olson, started August 2006.
//
// Copyright (C) 2006 Curtis L. Olson - http://www.flightgear.org/~curt
//
// 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 <cstdlib>
#include <cstring>
#include <simgear/debug/logstream.hxx>
#include <simgear/math/sg_geodesy.hxx>
#include <simgear/io/iochannel.hxx>
#include <simgear/timing/sg_time.hxx>
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
#include "AV400.hxx"
FGAV400::FGAV400() {
}
FGAV400::~FGAV400() {
}
// generate AV400 message
bool FGAV400::gen_message() {
// cout << "generating garmin message" << endl;
char msg_z[32], msg_A[32], msg_B[32], msg_C[32], msg_D[32];
char msg_Q[32], msg_T[32], msg_type2[256];
// the following could be implemented, but currently are unused
// char msg_E[32], msg_G[32], msg_I[32], msg_K[32], msg_L[32], msg_S[32];
// char msg_l[32];
char dir;
int deg;
double min;
// create msg_z
sprintf( msg_z, "z%05.0f\r\n", fdm.get_Altitude() );
// create msg_A
sprintf( msg_A, "A");
double latd = fdm.get_Latitude() * SGD_RADIANS_TO_DEGREES;
if ( latd < 0.0 ) {
latd = -latd;
dir = 'S';
} else {
dir = 'N';
}
deg = (int)latd;
min = (latd - (double)deg) * 60.0 * 100.0;
sprintf( msg_A, "A%c %02d %04.0f\r\n", dir, deg, min);
// create msg_B
double lond = fdm.get_Longitude() * SGD_RADIANS_TO_DEGREES;
if ( lond < 0.0 ) {
lond = -lond;
dir = 'W';
} else {
dir = 'E';
}
deg = (int)lond;
min = (lond - (double)deg) * 60.0 * 100.0;
sprintf( msg_B, "B%c %03d %04.0f\r\n", dir, deg, min);
// create msg_C
float magdeg = fgGetDouble( "/environment/magnetic-variation-deg" );
double vn = fgGetDouble( "/velocities/speed-north-fps" );
double ve = fgGetDouble( "/velocities/speed-east-fps" );
double gnd_trk_true = atan2( ve, vn ) * SGD_RADIANS_TO_DEGREES;
double gnd_trk_mag = gnd_trk_true - magdeg;
if ( gnd_trk_mag < 0.0 ) { gnd_trk_mag += 360.0; }
if ( gnd_trk_mag >= 360.0 ) { gnd_trk_mag -= 360.0; }
sprintf( msg_C, "C%03.0f\r\n", gnd_trk_mag);
// create msg_D
double speed_kt = sqrt( vn*vn + ve*ve ) * SG_FPS_TO_KT;
if ( speed_kt > 999.0 ) {
speed_kt = 999.0;
}
sprintf( msg_D, "D%03.0f\r\n", speed_kt);
// create msg_E (not implemented)
// create msg_G (not implemented)
// create msg_I (not implemented)
// create msg_K (not implemented)
// create msg_L (not implemented)
// create msg_Q
if ( magdeg < 0.0 ) {
magdeg = -magdeg;
dir = 'W';
} else {
dir = 'E';
}
sprintf( msg_Q, "Q%c%03.0f\r\n", dir, magdeg * 10.0 );
// create msg_S (not implemented)
// create msg_T
sprintf( msg_T, "T---------\r\n" );
// create msg_l (not implemented)
// sentence type 2
sprintf( msg_type2, "w01%c\r\n", (char)65 );
// assemble message
string sentence;
sentence += '\002'; // STX
sentence += msg_z; // altitude
sentence += msg_A; // latitude
sentence += msg_B; // longitude
sentence += msg_C; // ground track
sentence += msg_D; // ground speed (kt)
// sentence += "E-----\r\n";
// sentence += "G-----\r\n";
// sentence += "I----\r\n";
// sentence += "K-----\r\n";
// sentence += "L----\r\n";
sentence += msg_Q; // magvar
// sentence += "S-----\r\n";
sentence += msg_T; // end of type 1 messages (must be sent)
sentence += msg_type2; // type2 message
// sentence += "l------\r\n";
sentence += '\003'; // ETX
// cout << sentence;
length = sentence.length();
// cout << endl << "length = " << length << endl;
strncpy( buf, sentence.c_str(), length );
return true;
}
// parse AV400 message
bool FGAV400::parse_message() {
SG_LOG( SG_IO, SG_INFO, "parse garmin message" );
string msg = buf;
msg = msg.substr( 0, length );
SG_LOG( SG_IO, SG_INFO, "entire message = " << msg );
string::size_type begin_line, end_line, begin, end;
begin_line = begin = 0;
// extract out each line
end_line = msg.find("\n", begin_line);
while ( end_line != string::npos ) {
string line = msg.substr(begin_line, end_line - begin_line);
begin_line = end_line + 1;
SG_LOG( SG_IO, SG_INFO, " input line = " << line );
// leading character
string start = msg.substr(begin, 1);
++begin;
SG_LOG( SG_IO, SG_INFO, " start = " << start );
// sentence
end = msg.find(",", begin);
if ( end == string::npos ) {
return false;
}
string sentence = msg.substr(begin, end - begin);
begin = end + 1;
SG_LOG( SG_IO, SG_INFO, " sentence = " << sentence );
double lon_deg, lon_min, lat_deg, lat_min;
double lon, lat, speed, heading, altitude;
if ( sentence == "GPRMC" ) {
// time
end = msg.find(",", begin);
if ( end == string::npos ) {
return false;
}
string utc = msg.substr(begin, end - begin);
begin = end + 1;
SG_LOG( SG_IO, SG_INFO, " utc = " << utc );
// junk
end = msg.find(",", begin);
if ( end == string::npos ) {
return false;
}
string junk = msg.substr(begin, end - begin);
begin = end + 1;
SG_LOG( SG_IO, SG_INFO, " junk = " << junk );
// lat val
end = msg.find(",", begin);
if ( end == string::npos ) {
return false;
}
string lat_str = msg.substr(begin, end - begin);
begin = end + 1;
lat_deg = atof( lat_str.substr(0, 2).c_str() );
lat_min = atof( lat_str.substr(2).c_str() );
// lat dir
end = msg.find(",", begin);
if ( end == string::npos ) {
return false;
}
string lat_dir = msg.substr(begin, end - begin);
begin = end + 1;
lat = lat_deg + ( lat_min / 60.0 );
if ( lat_dir == "S" ) {
lat *= -1;
}
fdm.set_Latitude( lat * SGD_DEGREES_TO_RADIANS );
SG_LOG( SG_IO, SG_INFO, " lat = " << lat );
// lon val
end = msg.find(",", begin);
if ( end == string::npos ) {
return false;
}
string lon_str = msg.substr(begin, end - begin);
begin = end + 1;
lon_deg = atof( lon_str.substr(0, 3).c_str() );
lon_min = atof( lon_str.substr(3).c_str() );
// lon dir
end = msg.find(",", begin);
if ( end == string::npos ) {
return false;
}
string lon_dir = msg.substr(begin, end - begin);
begin = end + 1;
lon = lon_deg + ( lon_min / 60.0 );
if ( lon_dir == "W" ) {
lon *= -1;
}
fdm.set_Longitude( lon * SGD_DEGREES_TO_RADIANS );
SG_LOG( SG_IO, SG_INFO, " lon = " << lon );
#if 0
double sl_radius, lat_geoc;
sgGeodToGeoc( fdm.get_Latitude(),
fdm.get_Altitude(),
&sl_radius, &lat_geoc );
fdm.set_Geocentric_Position( lat_geoc,
fdm.get_Longitude(),
sl_radius + fdm.get_Altitude() );
#endif
// speed
end = msg.find(",", begin);
if ( end == string::npos ) {
return false;
}
string speed_str = msg.substr(begin, end - begin);
begin = end + 1;
speed = atof( speed_str.c_str() );
fdm.set_V_calibrated_kts( speed );
// fdm.set_V_ground_speed( speed );
SG_LOG( SG_IO, SG_INFO, " speed = " << speed );
// heading
end = msg.find(",", begin);
if ( end == string::npos ) {
return false;
}
string hdg_str = msg.substr(begin, end - begin);
begin = end + 1;
heading = atof( hdg_str.c_str() );
fdm.set_Euler_Angles( fdm.get_Phi(),
fdm.get_Theta(),
heading * SGD_DEGREES_TO_RADIANS );
SG_LOG( SG_IO, SG_INFO, " heading = " << heading );
} else if ( sentence == "PGRMZ" ) {
// altitude
end = msg.find(",", begin);
if ( end == string::npos ) {
return false;
}
string alt_str = msg.substr(begin, end - begin);
altitude = atof( alt_str.c_str() );
begin = end + 1;
// altitude units
end = msg.find(",", begin);
if ( end == string::npos ) {
return false;
}
string alt_units = msg.substr(begin, end - begin);
begin = end + 1;
if ( alt_units != "F" && alt_units != "f" ) {
altitude *= SG_METER_TO_FEET;
}
fdm.set_Altitude( altitude );
SG_LOG( SG_IO, SG_INFO, " altitude = " << altitude );
}
// printf("%.8f %.8f\n", lon, lat);
begin = begin_line;
end_line = msg.find("\n", begin_line);
}
return true;
}
// open hailing frequencies
bool FGAV400::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 FGAV400::process() {
SGIOChannel *io = get_io_channel();
if ( get_direction() == SG_IO_OUT ) {
gen_message();
if ( ! io->write( buf, length ) ) {
SG_LOG( SG_IO, SG_WARN, "Error writing data." );
return false;
}
} else if ( get_direction() == SG_IO_IN ) {
if ( (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." );
}
} else {
SG_LOG( SG_IO, SG_ALERT, "Error reading data." );
return false;
}
if ( (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." );
}
} else {
SG_LOG( SG_IO, SG_ALERT, "Error reading data." );
return false;
}
}
return true;
}
// close the channel
bool FGAV400::close() {
SGIOChannel *io = get_io_channel();
set_enabled( false );
if ( ! io->close() ) {
return false;
}
return true;
}

61
src/Network/AV400.hxx Normal file
View File

@@ -0,0 +1,61 @@
// AV400.hxx -- Garmin 400 series protocal class
//
// Written by Curtis Olson, started August 2006.
//
// Copyright (C) 2006 Curtis L. Olson - http://www.flightgear.org/~curt
//
// 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$
#ifndef _FG_AV400_HXX
#define _FG_AV400_HXX
#include <simgear/compiler.h>
#include <string>
#include "protocol.hxx"
#include <FDM/flightProperties.hxx>
class FGAV400 : public FGProtocol {
char buf[ FG_MAX_MSG_SIZE ];
int length;
public:
FGAV400();
~FGAV400();
bool gen_message();
bool parse_message();
// open hailing frequencies
bool open();
// process work for this port
bool process();
// close the channel
bool close();
FlightProperties fdm;
};
#endif // _FG_AV400_HXX

298
src/Network/AV400Sim.cxx Normal file
View File

@@ -0,0 +1,298 @@
// AV400Sim.cxx -- Garmin 400 series protocal class. This AV400Sim
// protocol generates the set of "simulator" commands a garmin 400
// series gps would expect as input in simulator mode. The AV400
// protocol generates the set of commands that a garmin 400 series gps
// would emit.
//
// Written by Curtis Olson, started Janauary 2009.
//
// Copyright (C) 2009 Curtis L. Olson - http://www.flightgear.org/~curt
//
// 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 <cstdlib>
#include <cstring>
#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 "AV400Sim.hxx"
FGAV400Sim::FGAV400Sim() {
fdm = new FlightProperties;
}
FGAV400Sim::~FGAV400Sim() {
delete fdm;
}
// generate AV400Sim message
bool FGAV400Sim::gen_message() {
// cout << "generating garmin message" << endl;
char msg_a[32], msg_b[32], msg_c[32], msg_d[32], msg_e[32];
char msg_f[32], msg_h[32], msg_i[32], msg_j[32], msg_k[32], msg_l[32], msg_r[32];
char msg_type2[256];
char dir;
int deg;
double min;
// create msg_a
double latd = fdm->get_Latitude() * SGD_RADIANS_TO_DEGREES;
if ( latd < 0.0 ) {
latd = -latd;
dir = 'S';
} else {
dir = 'N';
}
deg = (int)latd;
min = (latd - (double)deg) * 60.0 * 100.0;
sprintf( msg_a, "a%c %03d %04.0f\r\n", dir, deg, min);
// create msg_b
double lond = fdm->get_Longitude() * SGD_RADIANS_TO_DEGREES;
if ( lond < 0.0 ) {
lond = -lond;
dir = 'W';
} else {
dir = 'E';
}
deg = (int)lond;
min = (lond - (double)deg) * 60.0 * 100.0;
sprintf( msg_b, "b%c %03d %04.0f\r\n", dir, deg, min);
// create msg_c
double alt = fdm->get_Altitude();
if ( alt > 99999.0 ) { alt = 99999.0; }
sprintf( msg_c, "c%05.0f\r\n", alt );
// create msg_d
double ve_kts = fgGetDouble( "/velocities/speed-east-fps" ) * SG_FPS_TO_KT;
if ( ve_kts < 0.0 ) {
ve_kts = -ve_kts;
dir = 'W';
} else {
dir = 'E';
}
if ( ve_kts > 999.0 ) { ve_kts = 999.0; }
sprintf( msg_d, "d%c%03.0f\r\n", dir, ve_kts );
// create msg_e
double vn_kts = fgGetDouble( "/velocities/speed-north-fps" ) * SG_FPS_TO_KT;
if ( vn_kts < 0.0 ) {
vn_kts = -vn_kts;
dir = 'S';
} else {
dir = 'N';
}
if ( vn_kts > 999.0 ) { vn_kts = 999.0; }
sprintf( msg_e, "e%c%03.0f\r\n", dir, vn_kts );
// create msg_f
double climb_fpm = fgGetDouble( "/velocities/vertical-speed-fps" ) * 60;
if ( climb_fpm < 0.0 ) {
climb_fpm = -climb_fpm;
dir = 'D';
} else {
dir = 'U';
}
if ( climb_fpm > 9999.0 ) { climb_fpm = 9999.0; }
sprintf( msg_f, "f%c%04.0f\r\n", dir, climb_fpm );
// 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 );
// create msg_r
sprintf( msg_r, "rA\r\n" );
// sentence type 2
sprintf( msg_type2, "w01%c\r\n", (char)65 );
// assemble message
string sentence;
sentence += '\002'; // STX
sentence += msg_a; // latitude
sentence += msg_b; // longitude
sentence += msg_c; // gps altitude
sentence += msg_d; // ve kts
sentence += msg_e; // vn kts
sentence += msg_f; // climb fpm
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_r; // RAIM available
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 AV400Sim message
bool FGAV400Sim::parse_message() {
SG_LOG( SG_IO, SG_INFO, "parse AV400Sim 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/gps/cdi-deflection", 0.0);
} else {
int pos = atoi(num.c_str());
if ( side == "L" ) {
pos *= -1;
}
fgSetDouble("/instrumentation/gps/cdi-deflection",
(double)pos / 8.0);
fgSetBool("/instrumentation/gps/has-gs", false);
}
} else if ( ident == "k" ) {
string ind = msg.substr(1,1);
if ( ind == "T" ) {
fgSetBool("/instrumentation/gps/to-flag", true);
fgSetBool("/instrumentation/gps/from-flag", false);
} else if ( ind == "F" ) {
fgSetBool("/instrumentation/gps/to-flag", false);
fgSetBool("/instrumentation/gps/from-flag", true);
} else {
fgSetBool("/instrumentation/gps/to-flag", false);
fgSetBool("/instrumentation/gps/from-flag", false);
}
} else {
// SG_LOG( SG_IO, SG_ALERT, "unknown AV400Sim message = " << msg );
}
return true;
}
// open hailing frequencies
bool FGAV400Sim::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 FGAV400Sim::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 FGAV400Sim::close() {
SGIOChannel *io = get_io_channel();
set_enabled( false );
if ( ! io->close() ) {
return false;
}
return true;
}

65
src/Network/AV400Sim.hxx Normal file
View File

@@ -0,0 +1,65 @@
// AV400Sim.hxx -- Garmin 400 series protocal class. This AV400Sim
// protocol generates the set of "simulator" commands a garmin 400
// series gps would expect as input in simulator mode. The AV400
// protocol generates the set of commands that a garmin 400 series gps
// would emit.
//
// Written by Curtis Olson, started Januar 2009.
//
// Copyright (C) 2009 Curtis L. Olson - http://www.flightgear.org/~curt
//
// 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$
#ifndef _FG_AV400SIM_HXX
#define _FG_AV400SIM_HXX
#include <simgear/compiler.h>
#include <string>
#include "protocol.hxx"
class FlightProperties;
class FGAV400Sim : public FGProtocol {
char buf[ FG_MAX_MSG_SIZE ];
int length;
FlightProperties* fdm;
public:
FGAV400Sim();
~FGAV400Sim();
bool gen_message();
bool parse_message();
// open hailing frequencies
bool open();
// process work for this port
bool process();
// close the channel
bool close();
};
#endif // _FG_AV400SIM_HXX

1049
src/Network/AV400WSim.cxx Normal file

File diff suppressed because it is too large Load Diff

134
src/Network/AV400WSim.hxx Normal file
View File

@@ -0,0 +1,134 @@
// AV400Sim.hxx -- Garmin 400 series protocal class. This AV400Sim
// protocol generates the set of "simulator" commands a garmin 400
// series gps would expect as input in simulator mode. The AV400
// protocol generates the set of commands that a garmin 400 series gps
// would emit.
//
// Written by Curtis Olson, started Januar 2009.
//
// Copyright (C) 2009 Curtis L. Olson - http://www.flightgear.org/~curt
//
// 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$
#pragma once
#include <simgear/compiler.h>
#include <stdlib.h>
#include <string.h>
#include <string>
#include "protocol.hxx"
class FlightProperties;
//////////////////////////////////////////////////////////////////////////////
// Class FGAV400WSimA handles the input/output over the first serial port.
// This is very similar to the way previous Garmin non-WAAS models communicated
// but some items have been stripped out and just a minimal amount of
// info is necessary to be transmitted over this port.
class FGAV400WSimA : public FGProtocol {
char buf[ FG_MAX_MSG_SIZE ];
int length;
public:
FGAV400WSimA();
~FGAV400WSimA();
bool gen_message();
bool parse_message();
// open hailing frequencies
bool open();
// process work for this port
bool process();
// close the channel
bool close();
};
//////////////////////////////////////////////////////////////////////////////
// Class FGAV400WSimB handles the input/output over the second serial port
// which Garmin refers to as the "GPS Port". Some messages are handled on
// fixed cycle (usually 1 and 5 Hz) and some immediate responses are needed
// to certain messages upon requet by the GPS unit
class FGAV400WSimB : public FGProtocol {
char buf[ FG_MAX_MSG_SIZE ];
int length;
double hz2;
int hz2count;
int hz2cycles;
char flight_phase;
std::string hal;
std::string val;
std::string sbas_sel;
bool req_hostid;
bool req_sbas;
int outputctr;
FlightProperties* fdm;
static const int SOM_SIZE = 2;
static const int DEG_TO_MILLIARCSECS = ( 60 * 60 * 1000 );
// Flight Phases
static const int PHASE_OCEANIC = 4;
static const int PHASE_ENROUTE = 5;
static const int PHASE_TERM = 6;
static const int PHASE_NONPREC = 7;
static const int PHASE_LNAVVNAV = 8;
static const int PHASE_LPVLP = 9;
public:
FGAV400WSimB();
~FGAV400WSimB();
bool gen_hostid_message();
bool gen_sbas_message();
bool gen_Wh_message();
bool gen_Wx_message();
bool gen_Wt_message();
bool gen_Wm_message();
bool gen_Wv_message();
bool verify_checksum( std::string message, int datachars );
std::string asciitize_message( std::string message );
std::string buffer_to_string();
bool parse_message();
// open hailing frequencies
bool open();
// process work for this port
bool process();
// close the channel
bool close();
inline double get_hz2() const { return hz2; }
inline void set_hz2( double t ) { hz2 = t, hz2cycles = get_hz() / hz2; }
};

View File

@@ -0,0 +1,94 @@
include(FlightGearComponent)
set(SOURCES
ATC-Inputs.cxx
ATC-Main.cxx
ATC-Outputs.cxx
AV400.cxx
AV400Sim.cxx
AV400WSim.cxx
atlas.cxx
garmin.cxx
generic.cxx
HTTPClient.cxx
DNSClient.cxx
flarm.cxx
igc.cxx
joyclient.cxx
jsclient.cxx
lfsglass.cxx
native.cxx
native_structs.cxx
native_ctrls.cxx
native_fdm.cxx
native_gui.cxx
nmea.cxx
opengc.cxx
props.cxx
protocol.cxx
pve.cxx
ray.cxx
rul.cxx
)
set(HEADERS
ATC-Inputs.hxx
ATC-Main.hxx
ATC-Outputs.hxx
AV400.hxx
AV400Sim.hxx
AV400WSim.hxx
atlas.hxx
garmin.hxx
generic.hxx
HTTPClient.hxx
DNSClient.hxx
flarm.hxx
igc.hxx
joyclient.hxx
jsclient.hxx
lfsglass.hxx
native.hxx
native_ctrls.hxx
native_ctrls.hxx
native_fdm.hxx
native_gui.hxx
nmea.hxx
opengc.hxx
props.hxx
protocol.hxx
pve.hxx
ray.hxx
rul.hxx
)
if (CycloneDDS_FOUND)
list(APPEND SOURCES
dds_props.cxx
)
list(APPEND HEADERS
dds_props.hxx
)
endif()
if(ENABLE_IAX)
list(APPEND SOURCES fgcom.cxx)
list(APPEND HEADERS fgcom.hxx)
endif()
flightgear_component(Network "${SOURCES}" "${HEADERS}")
if (CycloneDDS_FOUND)
add_subdirectory(DDS)
endif()
if(RTI_FOUND)
add_subdirectory(HLA)
endif()
add_subdirectory(http)
if(ENABLE_SWIFT)
add_subdirectory(Swift)
endif()

View File

@@ -0,0 +1,34 @@
include(FlightGearComponent)
set(SOURCES
dds_ctrls.c
dds_gui.c
dds_fdm.c
dds_props.c
)
set(HEADERS
dds_ctrls.h
dds_fdm.h
dds_gui.h
dds_props.h
)
add_executable(fg_dds_log
WIN32
MACOSX_BUNDLE
fg_dds_log.cpp
${SOURCES}
)
setup_fgfs_libraries(fg_dds_log)
add_executable(fg_dds_prop
WIN32
MACOSX_BUNDLE
fg_dds_prop.cpp
${SOURCES}
)
setup_fgfs_libraries(fg_dds_prop)
flightgear_component(Network "${SOURCES}" "${HEADERS}")

12
src/Network/DDS/README Normal file
View File

@@ -0,0 +1,12 @@
Converting the OMG-IDL compatible interface definition files to C compatible
header and code files can be done using the package provided by CycloneDDS,
or any other OMG-IDL compatible IDL to C converter.
java -classpath "/usr/lib/cmake/CycloneDDS/idlc/idlc-jar-with-dependencies.jar"\
org.eclipse.cyclonedds.compilers.Idlc dds_<type>.idl
The .idl files as well as the genereated dds_ctrls, dds_fdm and dds_gui header
and souce files in this dirctory are in the Public Domain, and come with no
warranty.

View File

@@ -0,0 +1,88 @@
/****************************************************************
Generated by Eclipse Cyclone DDS IDL to C Translator
File name: dds_ctrls.c
Source: dds_ctrls.idl
Cyclone DDS: V0.7.0
*****************************************************************/
#include "dds_ctrls.h"
static const dds_key_descriptor_t FG_DDS_Ctrls_keys[1] =
{
{ "id", 0 }
};
static const uint32_t FG_DDS_Ctrls_ops [] =
{
DDS_OP_ADR | DDS_OP_TYPE_2BY | DDS_OP_FLAG_SGN | DDS_OP_FLAG_KEY, offsetof (FG_DDS_Ctrls, id),
DDS_OP_ADR | DDS_OP_TYPE_2BY | DDS_OP_FLAG_SGN, offsetof (FG_DDS_Ctrls, version),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_Ctrls, aileron),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_Ctrls, elevator),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_Ctrls, rudder),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_Ctrls, aileron_trim),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_Ctrls, elevator_trim),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_Ctrls, rudder_trim),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_Ctrls, flaps),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_Ctrls, spoilers),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_Ctrls, speedbrake),
DDS_OP_ADR | DDS_OP_TYPE_BOO, offsetof (FG_DDS_Ctrls, flaps_power),
DDS_OP_ADR | DDS_OP_TYPE_BOO, offsetof (FG_DDS_Ctrls, flap_motor_ok),
DDS_OP_ADR | DDS_OP_TYPE_2BY, offsetof (FG_DDS_Ctrls, num_engines),
DDS_OP_ADR | DDS_OP_TYPE_ARR | DDS_OP_SUBTYPE_BOO, offsetof (FG_DDS_Ctrls, master_bat), 4,
DDS_OP_ADR | DDS_OP_TYPE_ARR | DDS_OP_SUBTYPE_BOO, offsetof (FG_DDS_Ctrls, master_alt), 4,
DDS_OP_ADR | DDS_OP_TYPE_ARR | DDS_OP_SUBTYPE_4BY, offsetof (FG_DDS_Ctrls, magnetos), 4,
DDS_OP_ADR | DDS_OP_TYPE_ARR | DDS_OP_SUBTYPE_4BY, offsetof (FG_DDS_Ctrls, starter_power), 4,
DDS_OP_ADR | DDS_OP_TYPE_ARR | DDS_OP_SUBTYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_Ctrls, throttle), 4,
DDS_OP_ADR | DDS_OP_TYPE_ARR | DDS_OP_SUBTYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_Ctrls, mixture), 4,
DDS_OP_ADR | DDS_OP_TYPE_ARR | DDS_OP_SUBTYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_Ctrls, condition), 4,
DDS_OP_ADR | DDS_OP_TYPE_ARR | DDS_OP_SUBTYPE_4BY, offsetof (FG_DDS_Ctrls, fuel_pump_power), 4,
DDS_OP_ADR | DDS_OP_TYPE_ARR | DDS_OP_SUBTYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_Ctrls, prop_advance), 4,
DDS_OP_ADR | DDS_OP_TYPE_ARR | DDS_OP_SUBTYPE_2BY, offsetof (FG_DDS_Ctrls, feed_tank_to), 4,
DDS_OP_ADR | DDS_OP_TYPE_ARR | DDS_OP_SUBTYPE_BOO, offsetof (FG_DDS_Ctrls, reverse), 4,
DDS_OP_ADR | DDS_OP_TYPE_ARR | DDS_OP_SUBTYPE_BOO, offsetof (FG_DDS_Ctrls, engine_ok), 4,
DDS_OP_ADR | DDS_OP_TYPE_ARR | DDS_OP_SUBTYPE_BOO, offsetof (FG_DDS_Ctrls, mag_left_ok), 4,
DDS_OP_ADR | DDS_OP_TYPE_ARR | DDS_OP_SUBTYPE_BOO, offsetof (FG_DDS_Ctrls, mag_right_ok), 4,
DDS_OP_ADR | DDS_OP_TYPE_ARR | DDS_OP_SUBTYPE_BOO, offsetof (FG_DDS_Ctrls, spark_plugs_ok), 4,
DDS_OP_ADR | DDS_OP_TYPE_ARR | DDS_OP_SUBTYPE_2BY, offsetof (FG_DDS_Ctrls, oil_press_status), 4,
DDS_OP_ADR | DDS_OP_TYPE_ARR | DDS_OP_SUBTYPE_BOO, offsetof (FG_DDS_Ctrls, fuel_pump_ok), 4,
DDS_OP_ADR | DDS_OP_TYPE_2BY, offsetof (FG_DDS_Ctrls, num_tanks),
DDS_OP_ADR | DDS_OP_TYPE_ARR | DDS_OP_SUBTYPE_2BY, offsetof (FG_DDS_Ctrls, fuel_selector), 8,
DDS_OP_ADR | DDS_OP_TYPE_BOO, offsetof (FG_DDS_Ctrls, cross_feed),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_Ctrls, brake_left),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_Ctrls, brake_right),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_Ctrls, copilot_brake_left),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_Ctrls, copilot_brake_right),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_Ctrls, brake_parking),
DDS_OP_ADR | DDS_OP_TYPE_BOO, offsetof (FG_DDS_Ctrls, gear_handle),
DDS_OP_ADR | DDS_OP_TYPE_BOO, offsetof (FG_DDS_Ctrls, master_avionics),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_Ctrls, comm_1),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_Ctrls, comm_2),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_Ctrls, nav_1),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_Ctrls, nav_2),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_Ctrls, wind_speed_kt),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_Ctrls, wind_dir_deg),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_Ctrls, turbulence_norm),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_Ctrls, temp_c),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_Ctrls, press_inhg),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_Ctrls, hground),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_Ctrls, magvar),
DDS_OP_ADR | DDS_OP_TYPE_BOO, offsetof (FG_DDS_Ctrls, icing),
DDS_OP_ADR | DDS_OP_TYPE_4BY, offsetof (FG_DDS_Ctrls, speedup),
DDS_OP_ADR | DDS_OP_TYPE_2BY, offsetof (FG_DDS_Ctrls, freeze),
DDS_OP_RTS
};
const dds_topic_descriptor_t FG_DDS_Ctrls_desc =
{
sizeof (FG_DDS_Ctrls),
4u,
DDS_TOPIC_FIXED_KEY,
1u,
"FG::DDS_Ctrls",
FG_DDS_Ctrls_keys,
56,
FG_DDS_Ctrls_ops,
"<MetaData version=\"1.0.0\"><Module name=\"FG\"><Struct name=\"DDS_Ctrls\"><Member name=\"id\"><Short/></Member><Member name=\"version\"><Short/></Member><Member name=\"aileron\"><Float/></Member><Member name=\"elevator\"><Float/></Member><Member name=\"rudder\"><Float/></Member><Member name=\"aileron_trim\"><Float/></Member><Member name=\"elevator_trim\"><Float/></Member><Member name=\"rudder_trim\"><Float/></Member><Member name=\"flaps\"><Float/></Member><Member name=\"spoilers\"><Float/></Member><Member name=\"speedbrake\"><Float/></Member><Member name=\"flaps_power\"><Boolean/></Member><Member name=\"flap_motor_ok\"><Boolean/></Member><Member name=\"num_engines\"><UShort/></Member><Member name=\"master_bat\"><Array size=\"4\"><Boolean/></Array></Member><Member name=\"master_alt\"><Array size=\"4\"><Boolean/></Array></Member><Member name=\"magnetos\"><Array size=\"4\"><ULong/></Array></Member><Member name=\"starter_power\"><Array size=\"4\"><ULong/></Array></Member><Member name=\"throttle\"><Array size=\"4\"><Float/></Array></Member><Member name=\"mixture\"><Array size=\"4\"><Float/></Array></Member><Member name=\"condition\"><Array size=\"4\"><Float/></Array></Member><Member name=\"fuel_pump_power\"><Array size=\"4\"><ULong/></Array></Member><Member name=\"prop_advance\"><Array size=\"4\"><Float/></Array></Member><Member name=\"feed_tank_to\"><Array size=\"4\"><UShort/></Array></Member><Member name=\"reverse\"><Array size=\"4\"><Boolean/></Array></Member><Member name=\"engine_ok\"><Array size=\"4\"><Boolean/></Array></Member><Member name=\"mag_left_ok\"><Array size=\"4\"><Boolean/></Array></Member><Member name=\"mag_right_ok\"><Array size=\"4\"><Boolean/></Array></Member><Member name=\"spark_plugs_ok\"><Array size=\"4\"><Boolean/></Array></Member><Member name=\"oil_press_status\"><Array size=\"4\"><UShort/></Array></Member><Member name=\"fuel_pump_ok\"><Array size=\"4\"><Boolean/></Array></Member><Member name=\"num_tanks\"><UShort/></Member><Member name=\"fuel_selector\"><Array size=\"8\"><UShort/></Array></Member><Member name=\"cross_feed\"><Boolean/></Member><Member name=\"brake_left\"><Float/></Member><Member name=\"brake_right\"><Float/></Member><Member name=\"copilot_brake_left\"><Float/></Member><Member name=\"copilot_brake_right\"><Float/></Member><Member name=\"brake_parking\"><Float/></Member><Member name=\"gear_handle\"><Boolean/></Member><Member name=\"master_avionics\"><Boolean/></Member><Member name=\"comm_1\"><Float/></Member><Member name=\"comm_2\"><Float/></Member><Member name=\"nav_1\"><Float/></Member><Member name=\"nav_2\"><Float/></Member><Member name=\"wind_speed_kt\"><Float/></Member><Member name=\"wind_dir_deg\"><Float/></Member><Member name=\"turbulence_norm\"><Float/></Member><Member name=\"temp_c\"><Float/></Member><Member name=\"press_inhg\"><Float/></Member><Member name=\"hground\"><Float/></Member><Member name=\"magvar\"><Float/></Member><Member name=\"icing\"><Boolean/></Member><Member name=\"speedup\"><ULong/></Member><Member name=\"freeze\"><UShort/></Member></Struct></Module></MetaData>"
};

View File

@@ -0,0 +1,96 @@
/****************************************************************
Generated by Eclipse Cyclone DDS IDL to C Translator
File name: dds_ctrls.h
Source: dds_ctrls.idl
Cyclone DDS: V0.7.0
*****************************************************************/
#include "dds/ddsc/dds_public_impl.h"
#ifndef _DDSL_DDS_CTRLS_H_
#define _DDSL_DDS_CTRLS_H_
#ifdef __cplusplus
extern "C" {
#endif
#define FG_DDS_CTRLS_VERSION 1
#define FG_DDS_ENGINES 4
#define FG_DDS_WHEELS 16
#define FG_DDS_TANKS 8
typedef struct FG_DDS_Ctrls
{
int16_t id;
int16_t version;
float aileron;
float elevator;
float rudder;
float aileron_trim;
float elevator_trim;
float rudder_trim;
float flaps;
float spoilers;
float speedbrake;
bool flaps_power;
bool flap_motor_ok;
uint16_t num_engines;
bool master_bat[4];
bool master_alt[4];
uint32_t magnetos[4];
uint32_t starter_power[4];
float throttle[4];
float mixture[4];
float condition[4];
uint32_t fuel_pump_power[4];
float prop_advance[4];
uint16_t feed_tank_to[4];
bool reverse[4];
bool engine_ok[4];
bool mag_left_ok[4];
bool mag_right_ok[4];
bool spark_plugs_ok[4];
uint16_t oil_press_status[4];
bool fuel_pump_ok[4];
uint16_t num_tanks;
uint16_t fuel_selector[8];
bool cross_feed;
float brake_left;
float brake_right;
float copilot_brake_left;
float copilot_brake_right;
float brake_parking;
bool gear_handle;
bool master_avionics;
float comm_1;
float comm_2;
float nav_1;
float nav_2;
float wind_speed_kt;
float wind_dir_deg;
float turbulence_norm;
float temp_c;
float press_inhg;
float hground;
float magvar;
bool icing;
uint32_t speedup;
uint16_t freeze;
} FG_DDS_Ctrls;
extern const dds_topic_descriptor_t FG_DDS_Ctrls_desc;
#define FG_DDS_Ctrls__alloc() \
((FG_DDS_Ctrls*) dds_alloc (sizeof (FG_DDS_Ctrls)));
#define FG_DDS_Ctrls_free(d,o) \
dds_sample_free ((d), &FG_DDS_Ctrls_desc, (o))
#ifdef __cplusplus
}
#endif
#endif /* _DDSL_DDS_CTRLS_H_ */

View File

@@ -0,0 +1,113 @@
// format dfescription: https://www.omg.org/spec/IDL/4.2/PDF
// Adapted from net_ctrls.hxx
module FG
{
// defining it this way also generates accompanying #defines in the header file.
const short DDS_CTRLS_VERSION = 1;
const short DDS_ENGINES = 4;
const short DDS_WHEELS = 16;
const short DDS_TANKS = 8;
struct DDS_Ctrls
{
short id; // DDS id
short version; // packet version
// Aero controls
// net_ctrl.hxx defines doubles but flouts do have enough precission
float aileron; // -1 ... 1
float elevator; // -1 ... 1
float rudder; // -1 ... 1
float aileron_trim; // -1 ... 1
float elevator_trim; // -1 ... 1
float rudder_trim; // -1 ... 1
float flaps; // 0 ... 1
float spoilers;
float speedbrake;
// Aero control faults
boolean flaps_power; // true = power available
boolean flap_motor_ok;
// Engine controls
unsigned short num_engines; // number of valid engines
boolean master_bat[DDS_ENGINES];
boolean master_alt[DDS_ENGINES];
unsigned long magnetos[DDS_ENGINES];
unsigned long starter_power[DDS_ENGINES]; // true = starter power
float throttle[DDS_ENGINES]; // 0 ... 1
float mixture[DDS_ENGINES]; // 0 ... 1
float condition[DDS_ENGINES]; // 0 ... 1
unsigned long fuel_pump_power[DDS_ENGINES]; // true = on
float prop_advance[DDS_ENGINES]; // 0 ... 1
unsigned short feed_tank_to[4];
boolean reverse[4];
// Engine faults
boolean engine_ok[DDS_ENGINES];
boolean mag_left_ok[DDS_ENGINES];
boolean mag_right_ok[DDS_ENGINES];
boolean spark_plugs_ok[DDS_ENGINES]; // false = fouled plugs
unsigned short oil_press_status[DDS_ENGINES];// 0 = normal, 1 = low, 2 = full fail
boolean fuel_pump_ok[DDS_ENGINES];
// Fuel management
unsigned short num_tanks; // number of valid tanks
unsigned short fuel_selector[DDS_TANKS]; // false = off, true = on
// unsigned short xfer_pump[5]; // specifies transfer from array
// value tank to tank specified
// by int value
boolean cross_feed; // false = off, true = on
// Brake controls
float brake_left;
float brake_right;
float copilot_brake_left;
float copilot_brake_right;
float brake_parking;
// Landing Gear
boolean gear_handle; // true=gear handle down; false= gear handle up
// Switches
boolean master_avionics;
// nav and Comm
float comm_1;
float comm_2;
float nav_1;
float nav_2;
// wind and turbulance
float wind_speed_kt;
float wind_dir_deg;
float turbulence_norm;
// temp and pressure
float temp_c;
float press_inhg;
// other information about environment
float hground; // ground elevation (meters)
float magvar; // local magnetic variation in degs.
// hazards
boolean icing; // icing status could me much
// more complex but I'm
// starting simple here.
// simulation control
unsigned long speedup; // integer speedup multiplier
unsigned short freeze; // 0=normal
// 0x01=master
// 0x02=position
// 0x04=fuel
};
#pragma keylist DDS_Ctrls id
}; // module FG

96
src/Network/DDS/dds_fdm.c Normal file
View File

@@ -0,0 +1,96 @@
/****************************************************************
Generated by Eclipse Cyclone DDS IDL to C Translator
File name: dds_fdm.c
Source: dds_fdm.idl
Cyclone DDS: V0.7.0
*****************************************************************/
#include "dds_fdm.h"
static const dds_key_descriptor_t FG_DDS_FDM_keys[1] =
{
{ "id", 0 }
};
static const uint32_t FG_DDS_FDM_ops [] =
{
DDS_OP_ADR | DDS_OP_TYPE_2BY | DDS_OP_FLAG_SGN | DDS_OP_FLAG_KEY, offsetof (FG_DDS_FDM, id),
DDS_OP_ADR | DDS_OP_TYPE_2BY | DDS_OP_FLAG_SGN, offsetof (FG_DDS_FDM, version),
DDS_OP_ADR | DDS_OP_TYPE_8BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, longitude),
DDS_OP_ADR | DDS_OP_TYPE_8BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, latitude),
DDS_OP_ADR | DDS_OP_TYPE_8BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, altitude),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, agl),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, phi),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, theta),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, psi),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, alpha),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, beta),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, phidot),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, thetadot),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, psidot),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, vcas),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, climb_rate),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, v_north),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, v_east),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, v_down),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, v_body_u),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, v_body_v),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, v_body_w),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, A_X_pilot),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, A_Y_pilot),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, A_Z_pilot),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, stall_warning),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, slip_deg),
DDS_OP_ADR | DDS_OP_TYPE_2BY, offsetof (FG_DDS_FDM, num_engines),
DDS_OP_ADR | DDS_OP_TYPE_ARR | DDS_OP_SUBTYPE_2BY, offsetof (FG_DDS_FDM, eng_state), 4,
DDS_OP_ADR | DDS_OP_TYPE_ARR | DDS_OP_SUBTYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, rpm), 4,
DDS_OP_ADR | DDS_OP_TYPE_ARR | DDS_OP_SUBTYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, fuel_flow), 4,
DDS_OP_ADR | DDS_OP_TYPE_ARR | DDS_OP_SUBTYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, fuel_px), 4,
DDS_OP_ADR | DDS_OP_TYPE_ARR | DDS_OP_SUBTYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, egt), 4,
DDS_OP_ADR | DDS_OP_TYPE_ARR | DDS_OP_SUBTYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, cht), 4,
DDS_OP_ADR | DDS_OP_TYPE_ARR | DDS_OP_SUBTYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, mp_osi), 4,
DDS_OP_ADR | DDS_OP_TYPE_ARR | DDS_OP_SUBTYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, tit), 4,
DDS_OP_ADR | DDS_OP_TYPE_ARR | DDS_OP_SUBTYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, oil_temp), 4,
DDS_OP_ADR | DDS_OP_TYPE_ARR | DDS_OP_SUBTYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, oil_px), 4,
DDS_OP_ADR | DDS_OP_TYPE_2BY, offsetof (FG_DDS_FDM, num_tanks),
DDS_OP_ADR | DDS_OP_TYPE_ARR | DDS_OP_SUBTYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, fuel_quantity), 8,
DDS_OP_ADR | DDS_OP_TYPE_ARR | DDS_OP_SUBTYPE_BOO, offsetof (FG_DDS_FDM, tank_selected), 8,
DDS_OP_ADR | DDS_OP_TYPE_ARR | DDS_OP_SUBTYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, capacity_m3), 8,
DDS_OP_ADR | DDS_OP_TYPE_ARR | DDS_OP_SUBTYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, unusable_m3), 8,
DDS_OP_ADR | DDS_OP_TYPE_ARR | DDS_OP_SUBTYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, density_kgpm3), 8,
DDS_OP_ADR | DDS_OP_TYPE_ARR | DDS_OP_SUBTYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, level_m3), 8,
DDS_OP_ADR | DDS_OP_TYPE_2BY, offsetof (FG_DDS_FDM, num_wheels),
DDS_OP_ADR | DDS_OP_TYPE_ARR | DDS_OP_SUBTYPE_2BY, offsetof (FG_DDS_FDM, wow), 16,
DDS_OP_ADR | DDS_OP_TYPE_ARR | DDS_OP_SUBTYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, gear_pos), 16,
DDS_OP_ADR | DDS_OP_TYPE_ARR | DDS_OP_SUBTYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, gear_steer), 16,
DDS_OP_ADR | DDS_OP_TYPE_ARR | DDS_OP_SUBTYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, gear_compression), 16,
DDS_OP_ADR | DDS_OP_TYPE_8BY, offsetof (FG_DDS_FDM, cur_time),
DDS_OP_ADR | DDS_OP_TYPE_8BY, offsetof (FG_DDS_FDM, warp),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, visibility),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, elevator),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, elevator_trim_tab),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, left_flap),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, right_flap),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, left_aileron),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, right_aileron),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, rudder),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, nose_wheel),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, speedbrake),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_FDM, spoilers),
DDS_OP_RTS
};
const dds_topic_descriptor_t FG_DDS_FDM_desc =
{
sizeof (FG_DDS_FDM),
8u,
DDS_TOPIC_FIXED_KEY,
1u,
"FG::DDS_FDM",
FG_DDS_FDM_keys,
64,
FG_DDS_FDM_ops,
"<MetaData version=\"1.0.0\"><Module name=\"FG\"><Struct name=\"DDS_FDM\"><Member name=\"id\"><Short/></Member><Member name=\"version\"><Short/></Member><Member name=\"longitude\"><Double/></Member><Member name=\"latitude\"><Double/></Member><Member name=\"altitude\"><Double/></Member><Member name=\"agl\"><Float/></Member><Member name=\"phi\"><Float/></Member><Member name=\"theta\"><Float/></Member><Member name=\"psi\"><Float/></Member><Member name=\"alpha\"><Float/></Member><Member name=\"beta\"><Float/></Member><Member name=\"phidot\"><Float/></Member><Member name=\"thetadot\"><Float/></Member><Member name=\"psidot\"><Float/></Member><Member name=\"vcas\"><Float/></Member><Member name=\"climb_rate\"><Float/></Member><Member name=\"v_north\"><Float/></Member><Member name=\"v_east\"><Float/></Member><Member name=\"v_down\"><Float/></Member><Member name=\"v_body_u\"><Float/></Member><Member name=\"v_body_v\"><Float/></Member><Member name=\"v_body_w\"><Float/></Member><Member name=\"A_X_pilot\"><Float/></Member><Member name=\"A_Y_pilot\"><Float/></Member><Member name=\"A_Z_pilot\"><Float/></Member><Member name=\"stall_warning\"><Float/></Member><Member name=\"slip_deg\"><Float/></Member><Member name=\"num_engines\"><UShort/></Member><Member name=\"eng_state\"><Array size=\"4\"><UShort/></Array></Member><Member name=\"rpm\"><Array size=\"4\"><Float/></Array></Member><Member name=\"fuel_flow\"><Array size=\"4\"><Float/></Array></Member><Member name=\"fuel_px\"><Array size=\"4\"><Float/></Array></Member><Member name=\"egt\"><Array size=\"4\"><Float/></Array></Member><Member name=\"cht\"><Array size=\"4\"><Float/></Array></Member><Member name=\"mp_osi\"><Array size=\"4\"><Float/></Array></Member><Member name=\"tit\"><Array size=\"4\"><Float/></Array></Member><Member name=\"oil_temp\"><Array size=\"4\"><Float/></Array></Member><Member name=\"oil_px\"><Array size=\"4\"><Float/></Array></Member><Member name=\"num_tanks\"><UShort/></Member><Member name=\"fuel_quantity\"><Array size=\"8\"><Float/></Array></Member><Member name=\"tank_selected\"><Array size=\"8\"><Boolean/></Array></Member><Member name=\"capacity_m3\"><Array size=\"8\"><Float/></Array></Member><Member name=\"unusable_m3\"><Array size=\"8\"><Float/></Array></Member><Member name=\"density_kgpm3\"><Array size=\"8\"><Float/></Array></Member><Member name=\"level_m3\"><Array size=\"8\"><Float/></Array></Member><Member name=\"num_wheels\"><UShort/></Member><Member name=\"wow\"><Array size=\"16\"><UShort/></Array></Member><Member name=\"gear_pos\"><Array size=\"16\"><Float/></Array></Member><Member name=\"gear_steer\"><Array size=\"16\"><Float/></Array></Member><Member name=\"gear_compression\"><Array size=\"16\"><Float/></Array></Member><Member name=\"cur_time\"><ULongLong/></Member><Member name=\"warp\"><ULongLong/></Member><Member name=\"visibility\"><Float/></Member><Member name=\"elevator\"><Float/></Member><Member name=\"elevator_trim_tab\"><Float/></Member><Member name=\"left_flap\"><Float/></Member><Member name=\"right_flap\"><Float/></Member><Member name=\"left_aileron\"><Float/></Member><Member name=\"right_aileron\"><Float/></Member><Member name=\"rudder\"><Float/></Member><Member name=\"nose_wheel\"><Float/></Member><Member name=\"speedbrake\"><Float/></Member><Member name=\"spoilers\"><Float/></Member></Struct></Module></MetaData>"
};

104
src/Network/DDS/dds_fdm.h Normal file
View File

@@ -0,0 +1,104 @@
/****************************************************************
Generated by Eclipse Cyclone DDS IDL to C Translator
File name: dds_fdm.h
Source: dds_fdm.idl
Cyclone DDS: V0.7.0
*****************************************************************/
#include "dds/ddsc/dds_public_impl.h"
#ifndef _DDSL_DDS_FDM_H_
#define _DDSL_DDS_FDM_H_
#ifdef __cplusplus
extern "C" {
#endif
#define FG_DDS_FDM_VERSION 1
#define FG_DDS_ENGINES 4
#define FG_DDS_WHEELS 16
#define FG_DDS_TANKS 8
typedef struct FG_DDS_FDM
{
int16_t id;
int16_t version;
double longitude;
double latitude;
double altitude;
float agl;
float phi;
float theta;
float psi;
float alpha;
float beta;
float phidot;
float thetadot;
float psidot;
float vcas;
float climb_rate;
float v_north;
float v_east;
float v_down;
float v_body_u;
float v_body_v;
float v_body_w;
float A_X_pilot;
float A_Y_pilot;
float A_Z_pilot;
float stall_warning;
float slip_deg;
uint16_t num_engines;
uint16_t eng_state[4];
float rpm[4];
float fuel_flow[4];
float fuel_px[4];
float egt[4];
float cht[4];
float mp_osi[4];
float tit[4];
float oil_temp[4];
float oil_px[4];
uint16_t num_tanks;
float fuel_quantity[8];
bool tank_selected[8];
float capacity_m3[8];
float unusable_m3[8];
float density_kgpm3[8];
float level_m3[8];
uint16_t num_wheels;
uint16_t wow[16];
float gear_pos[16];
float gear_steer[16];
float gear_compression[16];
uint64_t cur_time;
uint64_t warp;
float visibility;
float elevator;
float elevator_trim_tab;
float left_flap;
float right_flap;
float left_aileron;
float right_aileron;
float rudder;
float nose_wheel;
float speedbrake;
float spoilers;
} FG_DDS_FDM;
extern const dds_topic_descriptor_t FG_DDS_FDM_desc;
#define FG_DDS_FDM__alloc() \
((FG_DDS_FDM*) dds_alloc (sizeof (FG_DDS_FDM)));
#define FG_DDS_FDM_free(d,o) \
dds_sample_free ((d), &FG_DDS_FDM_desc, (o))
#ifdef __cplusplus
}
#endif
#endif /* _DDSL_DDS_FDM_H_ */

106
src/Network/DDS/dds_fdm.idl Normal file
View File

@@ -0,0 +1,106 @@
// format dfescription: https://www.omg.org/spec/IDL/4.2/PDF
// Adapted from net_fdm.hxx
module FG
{
// defining it this way also generates accompanying #defines in the header file.
const short DDS_FDM_VERSION = 1;
// same as defined in net_ctrls.hxx
// this is different from net_fdm.hxx
const short DDS_ENGINES = 4;
const short DDS_WHEELS = 16;
const short DDS_TANKS = 8;
struct DDS_FDM
{
short id; // DDS id
short version; // packet version
// Positions
double longitude; // geodetic (radians)
double latitude; // geodetic (radians)
double altitude; // above sea level (meters)
float agl; // above ground level (meters)
float phi; // roll (radians)
float theta; // pitch (radians)
float psi; // yaw or true heading (radians)
float alpha; // angle of attack (radians)
float beta; // side slip angle (radians)
// Velocities
float phidot; // roll rate (radians/sec)
float thetadot; // pitch rate (radians/sec)
float psidot; // yaw rate (radians/sec)
float vcas; // calibrated airspeed
float climb_rate; // feet per second
float v_north; // north velocity in local/body frame, fps
float v_east; // east velocity in local/body frame, fps
float v_down; // down/vertical velocity in local/body frame, fps
float v_body_u; // ECEF velocity in body frame
float v_body_v; // ECEF velocity in body frame
float v_body_w; // ECEF velocity in body frame
// Accelerations
float A_X_pilot; // X accel in body frame ft/sec^2
float A_Y_pilot; // Y accel in body frame ft/sec^2
float A_Z_pilot; // Z accel in body frame ft/sec^2
// Stall
float stall_warning;// 0.0 - 1.0 indicating the amount of stall
float slip_deg; // slip ball deflection
// Pressure
// Engine status
unsigned short num_engines; // Number of valid engines
unsigned short eng_state[DDS_ENGINES];// Engine state (off, cranking, running)
float rpm[DDS_ENGINES]; // Engine RPM rev/min
float fuel_flow[DDS_ENGINES]; // Fuel flow gallons/hr
float fuel_px[DDS_ENGINES]; // Fuel pressure psi
float egt[DDS_ENGINES]; // Exhuast gas temp deg F
float cht[DDS_ENGINES]; // Cylinder head temp deg F
float mp_osi[DDS_ENGINES]; // Manifold pressure
float tit[DDS_ENGINES]; // Turbine Inlet Temperature
float oil_temp[DDS_ENGINES]; // Oil temp deg F
float oil_px[DDS_ENGINES]; // Oil pressure psi
// Consumables
unsigned short num_tanks; // Max number of fuel tanks
float fuel_quantity[DDS_TANKS]; // used by GPSsmooth and possibly others
boolean tank_selected[DDS_TANKS]; // selected, capacity, usable, density and level required for multiple-pc setups to work
float capacity_m3[DDS_TANKS];
float unusable_m3[DDS_TANKS];
float density_kgpm3[DDS_TANKS];
float level_m3[DDS_TANKS];
// Gear status
unsigned short num_wheels;
unsigned short wow[DDS_WHEELS];
float gear_pos[DDS_WHEELS];
float gear_steer[DDS_WHEELS];
float gear_compression[DDS_WHEELS];
// Environment
unsigned long long cur_time;// current unix time, 64-bit
unsigned long long warp; // offset in seconds to unix time
float visibility; // visibility in meters (for env. effects)
// Control surface positions (normalized values)
float elevator;
float elevator_trim_tab;
float left_flap;
float right_flap;
float left_aileron;
float right_aileron;
float rudder;
float nose_wheel;
float speedbrake;
float spoilers;
};
#pragma keylist DDS_FDM id
}; // module FG

View File

@@ -0,0 +1,10 @@
// dds_fwd.hxx
//
// This file is in the Public Domain, and comes with no warranty.
#pragma once
#include "dds_gui.h"
#include "dds_fdm.h"
#include "dds_ctrls.h"
#include "dds_props.h"

55
src/Network/DDS/dds_gui.c Normal file
View File

@@ -0,0 +1,55 @@
/****************************************************************
Generated by Eclipse Cyclone DDS IDL to C Translator
File name: dds_gui.c
Source: dds_gui.idl
Cyclone DDS: V0.7.0
*****************************************************************/
#include "dds_gui.h"
static const dds_key_descriptor_t FG_DDS_GUI_keys[1] =
{
{ "id", 0 }
};
static const uint32_t FG_DDS_GUI_ops [] =
{
DDS_OP_ADR | DDS_OP_TYPE_2BY | DDS_OP_FLAG_SGN | DDS_OP_FLAG_KEY, offsetof (FG_DDS_GUI, id),
DDS_OP_ADR | DDS_OP_TYPE_2BY | DDS_OP_FLAG_SGN, offsetof (FG_DDS_GUI, version),
DDS_OP_ADR | DDS_OP_TYPE_8BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_GUI, longitude),
DDS_OP_ADR | DDS_OP_TYPE_8BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_GUI, latitude),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_GUI, altitude),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_GUI, agl),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_GUI, phi),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_GUI, theta),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_GUI, psi),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_GUI, vcas),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_GUI, climb_rate),
DDS_OP_ADR | DDS_OP_TYPE_2BY, offsetof (FG_DDS_GUI, num_tanks),
DDS_OP_ADR | DDS_OP_TYPE_ARR | DDS_OP_SUBTYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_GUI, fuel_quantity), 8,
DDS_OP_ADR | DDS_OP_TYPE_8BY, offsetof (FG_DDS_GUI, cur_time),
DDS_OP_ADR | DDS_OP_TYPE_8BY, offsetof (FG_DDS_GUI, warp),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_GUI, ground_elev),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_GUI, tuned_freq),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_GUI, nav_radial),
DDS_OP_ADR | DDS_OP_TYPE_BOO, offsetof (FG_DDS_GUI, in_range),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_GUI, dist_nm),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_GUI, course_deviation_deg),
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP, offsetof (FG_DDS_GUI, gs_deviation_deg),
DDS_OP_RTS
};
const dds_topic_descriptor_t FG_DDS_GUI_desc =
{
sizeof (FG_DDS_GUI),
8u,
DDS_TOPIC_FIXED_KEY,
1u,
"FG::DDS_GUI",
FG_DDS_GUI_keys,
23,
FG_DDS_GUI_ops,
"<MetaData version=\"1.0.0\"><Module name=\"FG\"><Struct name=\"DDS_GUI\"><Member name=\"id\"><Short/></Member><Member name=\"version\"><Short/></Member><Member name=\"longitude\"><Double/></Member><Member name=\"latitude\"><Double/></Member><Member name=\"altitude\"><Float/></Member><Member name=\"agl\"><Float/></Member><Member name=\"phi\"><Float/></Member><Member name=\"theta\"><Float/></Member><Member name=\"psi\"><Float/></Member><Member name=\"vcas\"><Float/></Member><Member name=\"climb_rate\"><Float/></Member><Member name=\"num_tanks\"><UShort/></Member><Member name=\"fuel_quantity\"><Array size=\"8\"><Float/></Array></Member><Member name=\"cur_time\"><ULongLong/></Member><Member name=\"warp\"><ULongLong/></Member><Member name=\"ground_elev\"><Float/></Member><Member name=\"tuned_freq\"><Float/></Member><Member name=\"nav_radial\"><Float/></Member><Member name=\"in_range\"><Boolean/></Member><Member name=\"dist_nm\"><Float/></Member><Member name=\"course_deviation_deg\"><Float/></Member><Member name=\"gs_deviation_deg\"><Float/></Member></Struct></Module></MetaData>"
};

61
src/Network/DDS/dds_gui.h Normal file
View File

@@ -0,0 +1,61 @@
/****************************************************************
Generated by Eclipse Cyclone DDS IDL to C Translator
File name: dds_gui.h
Source: dds_gui.idl
Cyclone DDS: V0.7.0
*****************************************************************/
#include "dds/ddsc/dds_public_impl.h"
#ifndef _DDSL_DDS_GUI_H_
#define _DDSL_DDS_GUI_H_
#ifdef __cplusplus
extern "C" {
#endif
#define FG_DDS_GUI_VERSION 1
#define FG_DDS_TANKS 8
typedef struct FG_DDS_GUI
{
int16_t id;
int16_t version;
double longitude;
double latitude;
float altitude;
float agl;
float phi;
float theta;
float psi;
float vcas;
float climb_rate;
uint16_t num_tanks;
float fuel_quantity[8];
uint64_t cur_time;
uint64_t warp;
float ground_elev;
float tuned_freq;
float nav_radial;
bool in_range;
float dist_nm;
float course_deviation_deg;
float gs_deviation_deg;
} FG_DDS_GUI;
extern const dds_topic_descriptor_t FG_DDS_GUI_desc;
#define FG_DDS_GUI__alloc() \
((FG_DDS_GUI*) dds_alloc (sizeof (FG_DDS_GUI)));
#define FG_DDS_GUI_free(d,o) \
dds_sample_free ((d), &FG_DDS_GUI_desc, (o))
#ifdef __cplusplus
}
#endif
#endif /* _DDSL_DDS_GUI_H_ */

View File

@@ -0,0 +1,55 @@
// format dfescription: https://www.omg.org/spec/IDL/4.2/PDF
// Taken from net_gui.hxx
module FG
{
// defining it this way also generates accompanying #defines in the header file.
const short DDS_GUI_VERSION = 1;
// same as defined in net_ctrls.hxx
// this is different from net_gui.hxx
const short DDS_TANKS = 8;
struct DDS_GUI
{
short id;
short version;
// Positions
double longitude; // geodetic (radians)
double latitude; // geodetic (radians)
float altitude; // above sea level (meters)
float agl; // above ground level (meters)
float phi; // roll (radians)
float theta; // pitch (radians)
float psi; // yaw or true heading (radians)
// Velocities
float vcas;
float climb_rate; // feet per second
// Consumables
unsigned short num_tanks; // Max number of fuel tanks
float fuel_quantity[DDS_TANKS];
// Environment
unsigned long long cur_time;// current unix time
unsigned long long warp; // offset in seconds to unix time
float ground_elev; // ground elev (meters)
// Approach
float tuned_freq; // currently tuned frequency
float nav_radial; // target nav radial
boolean in_range; // tuned navaid is in range?
float dist_nm; // distance to tuned navaid in nautical miles
float course_deviation_deg; // degrees off target course
float gs_deviation_deg; // degrees off target glide slope
};
#pragma keylist DDS_GUI id
}; // module FG

View File

@@ -0,0 +1,47 @@
/****************************************************************
Generated by Eclipse Cyclone DDS IDL to C Translator
File name: dds_props.c
Source: dds_props.idl
Cyclone DDS: V0.7.0
*****************************************************************/
#include "dds_props.h"
static const dds_key_descriptor_t FG_DDS_prop_keys[1] =
{
{ "id", 0 }
};
static const uint32_t FG_DDS_prop_ops [] =
{
DDS_OP_ADR | DDS_OP_TYPE_4BY | DDS_OP_FLAG_SGN | DDS_OP_FLAG_KEY, offsetof (FG_DDS_prop, id),
DDS_OP_ADR | DDS_OP_TYPE_1BY, offsetof (FG_DDS_prop, version),
DDS_OP_ADR | DDS_OP_TYPE_BOO, offsetof (FG_DDS_prop, mode),
DDS_OP_ADR | DDS_OP_TYPE_UNI | DDS_OP_SUBTYPE_4BY | DDS_OP_FLAG_SGN, offsetof (FG_DDS_prop, val._d), 9u, (31u << 16) + 4u,
DDS_OP_JEQ | DDS_OP_TYPE_BOO | 0, FG_DDS_BOOL, offsetof (FG_DDS_prop, val._u.Bool),
DDS_OP_JEQ | DDS_OP_TYPE_4BY | DDS_OP_FLAG_SGN | 0, FG_DDS_NONE, offsetof (FG_DDS_prop, val._u.Int32),
DDS_OP_JEQ | DDS_OP_TYPE_4BY | DDS_OP_FLAG_SGN | 0, FG_DDS_INT, offsetof (FG_DDS_prop, val._u.Int32),
DDS_OP_JEQ | DDS_OP_TYPE_8BY | DDS_OP_FLAG_SGN | 0, FG_DDS_LONG, offsetof (FG_DDS_prop, val._u.Int64),
DDS_OP_JEQ | DDS_OP_TYPE_4BY | DDS_OP_FLAG_FP | 0, FG_DDS_FLOAT, offsetof (FG_DDS_prop, val._u.Float32),
DDS_OP_JEQ | DDS_OP_TYPE_8BY | DDS_OP_FLAG_FP | 0, FG_DDS_DOUBLE, offsetof (FG_DDS_prop, val._u.Float64),
DDS_OP_JEQ | DDS_OP_TYPE_STR | 0, FG_DDS_ALIAS, offsetof (FG_DDS_prop, val._u.String),
DDS_OP_JEQ | DDS_OP_TYPE_STR | 0, FG_DDS_STRING, offsetof (FG_DDS_prop, val._u.String),
DDS_OP_JEQ | DDS_OP_TYPE_STR | 0, FG_DDS_UNSPECIFIED, offsetof (FG_DDS_prop, val._u.String),
DDS_OP_ADR | DDS_OP_TYPE_ARR | DDS_OP_SUBTYPE_1BY, offsetof (FG_DDS_prop, guid), 16,
DDS_OP_RTS
};
const dds_topic_descriptor_t FG_DDS_prop_desc =
{
sizeof (FG_DDS_prop),
8u,
DDS_TOPIC_FIXED_KEY | DDS_TOPIC_NO_OPTIMIZE | DDS_TOPIC_CONTAINS_UNION,
1u,
"FG::DDS_prop",
FG_DDS_prop_keys,
15,
FG_DDS_prop_ops,
"<MetaData version=\"1.0.0\"><Module name=\"FG\"><Enum name=\"propType\"><Element name=\"DDS_NONE\" value=\"0\"/><Element name=\"DDS_ALIAS\" value=\"1\"/><Element name=\"DDS_BOOL\" value=\"2\"/><Element name=\"DDS_INT\" value=\"3\"/><Element name=\"DDS_LONG\" value=\"4\"/><Element name=\"DDS_FLOAT\" value=\"5\"/><Element name=\"DDS_DOUBLE\" value=\"6\"/><Element name=\"DDS_STRING\" value=\"7\"/><Element name=\"DDS_UNSPECIFIED\" value=\"8\"/></Enum><Union name=\"propValue\"><SwitchType><Type name=\"propType\"/></SwitchType><Case name=\"Bool\"><Boolean/><Label value=\"DDS_BOOL\"/></Case><Case name=\"Int32\"><Long/><Label value=\"DDS_NONE\"/><Label value=\"DDS_INT\"/></Case><Case name=\"Int64\"><LongLong/><Label value=\"DDS_LONG\"/></Case><Case name=\"Float32\"><Float/><Label value=\"DDS_FLOAT\"/></Case><Case name=\"Float64\"><Double/><Label value=\"DDS_DOUBLE\"/></Case><Case name=\"String\"><String/><Label value=\"DDS_ALIAS\"/><Label value=\"DDS_STRING\"/><Label value=\"DDS_UNSPECIFIED\"/></Case></Union><Struct name=\"DDS_prop\"><Member name=\"id\"><Long/></Member><Member name=\"version\"><Octet/></Member><Member name=\"mode\"><Boolean/></Member><Member name=\"val\"><Type name=\"propValue\"/></Member><Member name=\"guid\"><Array size=\"16\"><Octet/></Array></Member></Struct></Module></MetaData>"
};

View File

@@ -0,0 +1,79 @@
/****************************************************************
Generated by Eclipse Cyclone DDS IDL to C Translator
File name: dds_props.h
Source: dds_props.idl
Cyclone DDS: V0.7.0
*****************************************************************/
#include "dds/ddsc/dds_public_impl.h"
#ifndef _DDSL_DDS_PROPS_H_
#define _DDSL_DDS_PROPS_H_
#ifdef __cplusplus
extern "C" {
#endif
#define FG_DDS_PROP_VERSION 0
#define FG_DDS_PROP_REQUEST -1
#define FG_DDS_MODE_READ (0)
#define FG_DDS_MODE_WRITE (1)
typedef enum FG_propType
{
FG_DDS_NONE,
FG_DDS_ALIAS,
FG_DDS_BOOL,
FG_DDS_INT,
FG_DDS_LONG,
FG_DDS_FLOAT,
FG_DDS_DOUBLE,
FG_DDS_STRING,
FG_DDS_UNSPECIFIED
} FG_propType;
#define FG_propType__alloc() \
((FG_propType*) dds_alloc (sizeof (FG_propType)));
typedef struct FG_propValue
{
FG_propType _d;
union
{
bool Bool;
int32_t Int32;
int64_t Int64;
float Float32;
double Float64;
char * String;
} _u;
} FG_propValue;
#define FG_propValue__alloc() \
((FG_propValue*) dds_alloc (sizeof (FG_propValue)));
typedef struct FG_DDS_prop
{
int32_t id;
uint8_t version;
bool mode;
FG_propValue val;
uint8_t guid[16];
} FG_DDS_prop;
extern const dds_topic_descriptor_t FG_DDS_prop_desc;
#define FG_DDS_prop__alloc() \
((FG_DDS_prop*) dds_alloc (sizeof (FG_DDS_prop)));
#define FG_DDS_prop_free(d,o) \
dds_sample_free ((d), &FG_DDS_prop_desc, (o))
#ifdef __cplusplus
}
#endif
#endif /* _DDSL_DDS_PROPS_H_ */

View File

@@ -0,0 +1,90 @@
// format dfescription: https://www.omg.org/spec/IDL/4.2/PDF
// Adapted from net_fdm.hxx
module FG
{
// defining it this way also generates accompanying #defines in the header file.
const octet DDS_PROP_VERSION = 0;
const short DDS_PROP_REQUEST = -1;
const boolean DDS_MODE_READ = 0;
const boolean DDS_MODE_WRITE = 1;
enum propType
{
DDS_NONE, // The node hasn't been assigned a value yet
DDS_ALIAS, // The node "points" to another node
DDS_BOOL,
DDS_INT, // 32-bit integer
DDS_LONG, // 64-bit integer
DDS_FLOAT, // 32-bit floating point number
DDS_DOUBLE, // 64-bit floating point number
DDS_STRING, // UTF-8 string
DDS_UNSPECIFIED // Resolves to STRING
};
union propValue switch ( propType )
{
case DDS_BOOL:
boolean Bool;
case DDS_NONE:
case DDS_INT:
long Int32;
case DDS_LONG:
long long Int64;
case DDS_FLOAT:
float Float32;
case DDS_DOUBLE:
double Float64;
case DDS_ALIAS:
case DDS_STRING:
case DDS_UNSPECIFIED:
string String;
};
// Initial property request sequence
// for properties where the id is not yet known:
// 1. Set id to FG_DDS_PROP_REQUEST
// 2. Set version to FG_DDS_PROP_VERSION
// 3. Set mode to FG_DDS_MODE_READ
// 4. set guid to the 16-byte participants GUID
// 5. Set val type to FG_DDS_STRING
// 6. Set val String to the propery path
// 7. Send the package.
//
// 8. Wait for an answer
// * Check whether guid matches the participants GUID.
// * The index of the requested propery path is stored in the id variable
// which should be used by successive request as the id for that property.
// * The val union holds the value of the propery.
//
// Successive property request sequence:
// 1. Make sure id is to the id of the requested property
// 2. Make sure version is to FG_DDS_PROP_VERSION
// 3. Set mode to FG_DDS_MODE_READ or FG_DDS_MODE_WRITE
// 3a. Optionally set the val union to match the property to overwrite.
// 4. Send the package.
//
// 5. Wait for an answer
// * Check whether id matches the requested property id.
// * The val union holds the value of the propery.
struct DDS_prop
{
long id; // 32-bit property index and DDS id.
octet version; // 8-bit sample-type version number.
boolean mode; // FG_DDS_MODE_READ or FG_DDS_MODE_WRITE
propValue val; // property value or path.
// A sequence could be used here but unfortunatelly it's initializer
// already is 10-bytes in size.
octet guid[16];
};
#pragma keylist DDS_prop id
}; // module FG

View File

@@ -0,0 +1,170 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <map>
#include <simgear/io/SGDataDistributionService.hxx>
#include "dds_fwd.hxx"
/* An array of one message(aka sample in dds terms) will be used. */
#define MAX_SAMPLES 1
#ifndef _WIN32
# include <termios.h>
void set_mode(int want_key)
{
static struct termios tios_old, tios_new;
if (!want_key) {
tcsetattr(STDIN_FILENO, TCSANOW, &tios_old);
return;
}
tcgetattr(STDIN_FILENO, &tios_old);
tios_new = tios_old;
tios_new.c_lflag &= ~(ICANON | ECHO);
tcsetattr(STDIN_FILENO, TCSANOW, &tios_new);
}
int get_key()
{
int c = 0;
struct timeval tv;
fd_set fs;
tv.tv_usec = tv.tv_sec = 0;
FD_ZERO(&fs);
FD_SET(STDIN_FILENO, &fs);
select(STDIN_FILENO + 1, &fs, 0, 0, &tv);
if (FD_ISSET(STDIN_FILENO, &fs)) {
c = getchar();
}
return c;
}
#else
# include <conio.h>
int get_key()
{
if (kbhit()) {
return getch();
}
return 0;
}
void set_mode(int want_key)
{
}
#endif
int main()
{
std::map<std::string, SG_DDS_Topic*> topics;
SG_DDS participant;
FG_DDS_GUI gui;
topics["gui"] = new SG_DDS_Topic(gui, &FG_DDS_GUI_desc);
participant.add(topics["gui"], SG_IO_IN);
FG_DDS_FDM fdm;
topics["fdm"] = new SG_DDS_Topic(fdm, &FG_DDS_FDM_desc);
participant.add(topics["fdm"], SG_IO_IN);
FG_DDS_Ctrls ctrls;
topics["ctrls"] = new SG_DDS_Topic(ctrls, &FG_DDS_Ctrls_desc);
participant.add(topics["ctrls"], SG_IO_IN);
FG_DDS_prop prop;
topics["prop"] = new SG_DDS_Topic(prop, &FG_DDS_prop_desc);
participant.add(topics["prop"], SG_IO_IN);
set_mode(1);
while(participant.wait(0.1f))
{
if (topics["gui"]->read()) {
printf("=== [fg_dds_log] Received : ");
printf("GUI Message:\n");
printf(" version: %i\n", gui.version);
printf(" tuned_freq: %lf\n", gui.longitude);
printf(" nav_radial: %lf\n", gui.latitude);
printf(" dist_nm: %lf\n", gui.altitude);
}
if (topics["fdm"]->read()) {
printf("=== [fg_dds_log] Received : ");
printf("FDM Message:\n");
printf(" version: %i\n", fdm.version);
printf(" longitude: %lf\n", fdm.longitude);
printf(" latitude: %lf\n", fdm.latitude);
printf(" altitude: %lf\n", fdm.altitude);
}
if (topics["ctrls"]->read()) {
printf("=== [fg_dds_log] Received : ");
printf("Ctrls Message:\n");
printf(" version: %i\n", ctrls.version);
printf(" aileron: %lf\n", ctrls.aileron);
printf(" elevator: %lf\n", ctrls.elevator);
printf(" rudder: %lf\n", ctrls.rudder);
}
if (topics["prop"]->read() &&
prop.version == FG_DDS_PROP_VERSION )
{
printf("=== [fg_dds_log] Received : ");
printf("Prop Message:\n");
printf(" version: %i\n", prop.version);
printf(" mode: %s\n", prop.mode ? "write" : "read");
printf(" id: %i\n", prop.id);
if (prop.id == FG_DDS_PROP_REQUEST) {
printf(" path: %s\n", prop.val._u.String);
printf("GUID: ");
for(int i=0; i<16; ++i)
printf("%X ", prop.guid[i]);
printf("\n");
}
else
{
switch(prop.val._d)
{
case FG_DDS_BOOL:
printf(" type: bool");
printf(" value: %i\n", prop.val._u.Bool);
break;
case FG_DDS_INT:
printf(" type: int");
printf(" value: %i\n", prop.val._u.Int32);
break;
case FG_DDS_LONG:
printf(" type: long");
printf(" value: %li\n", prop.val._u.Int64);
break;
case FG_DDS_FLOAT:
printf(" type: float");
printf(" value: %f\n", prop.val._u.Float32);
break;
case FG_DDS_DOUBLE:
printf(" type: double");
printf(" value: %lf\n", prop.val._u.Float64);
break;
case FG_DDS_STRING:
printf(" type: string");
printf(" value: %s\n", prop.val._u.String);
break;
default:
break;
}
}
}
fflush(stdout);
if (get_key()) break;
}
set_mode(0);
return EXIT_SUCCESS;
}

View File

@@ -0,0 +1,99 @@
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <simgear/io/SGDataDistributionService.hxx>
#include "dds_props.h"
/* An array of one message(aka sample in dds terms) will be used. */
#define MAX_SAMPLES 1
int main()
{
SG_DDS participant;
FG_DDS_prop prop;
SG_DDS_Topic *topic = new SG_DDS_Topic(prop, &FG_DDS_prop_desc);
participant.add(topic, SG_IO_BI);
dds_guid_t guid = topic->get_guid();
memcpy(prop.guid, guid.v, 16);
printf("GUID: ");
for(int i=0; i<16; ++i)
printf("%X ", prop.guid[i]);
printf("\n");
char path[256];
printf("\nType 'q' to quit\n");
do
{
printf("Property path or id: ");
int len = scanf("%255s", path);
if (len == EOF) continue;
if (*path == 'q') break;
char *end;
int id = strtol(path, &end, 10);
prop.id = (end == path) ? FG_DDS_PROP_REQUEST : id;
prop.version = FG_DDS_PROP_VERSION;
prop.mode = FG_DDS_MODE_READ;
prop.val._d = FG_DDS_STRING;
prop.val._u.String = path;
topic->write();
participant.wait();
if (topic->read() &&
prop.version == FG_DDS_PROP_VERSION &&
prop.id != FG_DDS_PROP_REQUEST)
{
printf("\nReceived:\n");
switch(prop.val._d)
{
case FG_DDS_NONE:
printf(" type: none");
break;
case FG_DDS_ALIAS:
printf(" type: alias");
printf(" value: %s\n", prop.val._u.String);
break;
case FG_DDS_BOOL:
printf(" type: bool");
printf(" value: %i\n", prop.val._u.Bool);
break;
case FG_DDS_INT:
printf(" type: int");
printf(" value: %i\n", prop.val._u.Int32);
break;
case FG_DDS_LONG:
printf(" type: long");
printf(" value: %li\n", prop.val._u.Int64);
break;
case FG_DDS_FLOAT:
printf(" type: float");
printf(" value: %f\n", prop.val._u.Float32);
break;
case FG_DDS_DOUBLE:
printf(" type: double");
printf(" value: %lf\n", prop.val._u.Float64);
break;
case FG_DDS_STRING:
printf(" type: string");
printf(" value: %s\n", prop.val._u.String);
break;
case FG_DDS_UNSPECIFIED:
printf(" type: unspecified");
printf(" value: %s\n", prop.val._u.String);
break;
default:
break;
}
}
} while(true);
return EXIT_SUCCESS;
}

76
src/Network/DNSClient.cxx Normal file
View File

@@ -0,0 +1,76 @@
// HDNSClient.cxx -- Singleton DNS client object
//
// Written by James Turner, started April 2012.
// Based on HTTPClient from James Turner
// Copyright (C) 2012 James Turner
//
// 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.
#include "config.h"
#include "DNSClient.hxx"
#include <cassert>
#include <Main/fg_props.hxx>
#include <simgear/sg_inlines.h>
using namespace simgear;
FGDNSClient::FGDNSClient() :
_inited(false)
{
}
FGDNSClient::~FGDNSClient()
{
}
void FGDNSClient::init()
{
if (_inited) {
return;
}
_dns.reset( new simgear::DNS::Client() );
_inited = true;
}
void FGDNSClient::postinit()
{
}
void FGDNSClient::shutdown()
{
_dns.reset();
_inited = false;
}
void FGDNSClient::update(double)
{
_dns->update();
}
void FGDNSClient::makeRequest(const simgear::DNS::Request_ptr& req)
{
_dns->makeRequest(req);
}
// Register the subsystem.
SGSubsystemMgr::Registrant<FGDNSClient> registrantFGDNSClient;

55
src/Network/DNSClient.hxx Normal file
View File

@@ -0,0 +1,55 @@
// DNSClient.hxx -- Singleton DNS client object
//
// Written by Torsten Dreyer, started November 2016.
// Based on HTTPClient from James Turner
// Copyright (C) 2012 James Turner
//
// 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.
#ifndef FG_DNS_CLIENT_HXX
#define FG_DNS_CLIENT_HXX
#include <simgear/structure/subsystem_mgr.hxx>
#include <simgear/io/DNSClient.hxx>
#include <memory>
class FGDNSClient : public SGSubsystem
{
public:
FGDNSClient();
virtual ~FGDNSClient();
// Subsystem API.
void init() override;
void postinit() override;
void shutdown() override;
void update(double) override;
// Subsystem identification.
static const char* staticSubsystemClassId() { return "dns"; }
void makeRequest(const simgear::DNS::Request_ptr& req);
// simgear::HTTP::Client* client() { return _http.get(); }
// simgear::HTTP::Client const* client() const { return _http.get(); }
private:
bool _inited;
std::unique_ptr<simgear::DNS::Client> _dns;
};
#endif // FG_DNS_CLIENT_HXX

View File

@@ -0,0 +1,7 @@
include(FlightGearComponent)
set(SOURCES
hla.cxx
)
flightgear_component(HLA "${SOURCES}")

1327
src/Network/HLA/hla.cxx Normal file

File diff suppressed because it is too large Load Diff

56
src/Network/HLA/hla.hxx Normal file
View File

@@ -0,0 +1,56 @@
//
// Copyright (C) 2009 - 2010 Mathias Fröhlich <Mathias.Froehlich@web.de>
//
// 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.
//
#ifndef _FG_HLA_HXX
#define _FG_HLA_HXX
#include <simgear/compiler.h>
#include <simgear/structure/SGSharedPtr.hxx>
#include <string>
#include <vector>
#include <Network/protocol.hxx>
class FGHLA : public FGProtocol {
public:
FGHLA(const std::vector<std::string>& tokens);
virtual ~FGHLA();
virtual bool open();
virtual bool process();
virtual bool close();
private:
/// All the utility classes we need currently
class XMLConfigReader;
class MPUpdateCallback;
class MPReflectCallback;
class MultiplayerObjectInstance;
class MultiplayerObjectClass;
class Federate;
/// The configuration parameters extracted from the tokens in the constructor
std::string _objectModelConfig;
std::string _federation;
std::string _federate;
/// The toplevel rti class
SGSharedPtr<Federate> _hlaFederate;
};
#endif // _FG_HLA_HXX

337
src/Network/HTTPClient.cxx Normal file
View File

@@ -0,0 +1,337 @@
// HTTPClient.cxx -- Singleton HTTP client object
//
// Written by James Turner, started April 2012.
//
// Copyright (C) 2012 James Turner
//
// 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.
#include "config.h"
#include "HTTPClient.hxx"
#include <cassert>
#include <Main/fg_props.hxx>
#include <simgear/sg_inlines.h>
#include <simgear/package/Root.hxx>
#include <simgear/package/Catalog.hxx>
#include <simgear/package/Delegate.hxx>
#include <simgear/package/Install.hxx>
#include <simgear/package/Package.hxx>
#include <simgear/nasal/cppbind/from_nasal.hxx>
#include <simgear/nasal/cppbind/to_nasal.hxx>
#include <simgear/nasal/cppbind/NasalHash.hxx>
#include <simgear/nasal/cppbind/Ghost.hxx>
#include <Scripting/NasalSys.hxx>
using namespace simgear;
typedef nasal::Ghost<pkg::RootRef> NasalPackageRoot;
typedef nasal::Ghost<pkg::PackageRef> NasalPackage;
typedef nasal::Ghost<pkg::CatalogRef> NasalCatalog;
typedef nasal::Ghost<pkg::InstallRef> NasalInstall;
static const char* OFFICIAL_CATALOG_ID = "org.flightgear.fgaddon.trunk";
// fallback URL is used when looking up a version-specific catalog fails
static const char* FALLBACK_CATALOG_URL = "http://mirrors.ibiblio.org/flightgear/ftp/Aircraft-trunk/catalog.xml";
namespace {
std::string _getDefaultCatalogId()
{
return fgGetString("/sim/package-system/default-catalog/id",
OFFICIAL_CATALOG_ID);
}
pkg::CatalogRef getDefaultCatalog()
{
if (!globals->packageRoot())
return pkg::CatalogRef();
return globals->packageRoot()->getCatalogById(_getDefaultCatalogId());
}
std::string _getDefaultCatalogUrl()
{
return fgGetString("/sim/package-system/default-catalog/url",
"http://mirrors.ibiblio.org/flightgear/ftp/" FLIGHTGEAR_VERSION "/catalog.xml");
}
} // of anonymous namespace
FGHTTPClient::FGHTTPClient() :
_inited(false)
{
}
FGHTTPClient::~FGHTTPClient()
{
}
FGHTTPClient* FGHTTPClient::getOrCreate()
{
auto ext = globals->get_subsystem<FGHTTPClient>();
if (ext) {
return ext;
}
ext = globals->add_new_subsystem<FGHTTPClient>(SGSubsystemMgr::GENERAL);
ext->init();
return ext;
}
void FGHTTPClient::init()
{
// launcher may need to setup HTTP access abnormally early, so
// guard against duplicate inits
if (_inited) {
return;
}
_http.reset(new simgear::HTTP::Client);
std::string proxyHost(fgGetString("/sim/presets/proxy/host"));
int proxyPort(fgGetInt("/sim/presets/proxy/port"));
std::string proxyAuth(fgGetString("/sim/presets/proxy/auth"));
if (!proxyHost.empty()) {
_http->setProxy(proxyHost, proxyPort, proxyAuth);
}
pkg::Root* packageRoot = globals->packageRoot();
if (packageRoot) {
// package system needs access to the HTTP engine too
packageRoot->setHTTPClient(_http.get());
// start a refresh now
// setting 'force' true to work around the problem where a slightly stale
// catalog exists, but aircraft are modified - this causes an MD5 sum
// mismatch
packageRoot->refresh(true);
}
_inited = true;
}
bool FGHTTPClient::isDefaultCatalogInstalled() const
{
return getDefaultCatalog().valid();
}
pkg::CatalogRef FGHTTPClient::addDefaultCatalog()
{
pkg::CatalogRef defaultCatalog = getDefaultCatalog();
if (!defaultCatalog.valid()) {
auto cat = pkg::Catalog::createFromUrl(globals->packageRoot(), getDefaultCatalogUrl());
return cat;
}
return defaultCatalog;
}
std::string FGHTTPClient::getDefaultCatalogId() const
{
return _getDefaultCatalogId();
}
std::string FGHTTPClient::getDefaultCatalogUrl() const
{
return _getDefaultCatalogUrl();
}
std::string FGHTTPClient::getDefaultCatalogFallbackUrl() const
{
return std::string(FALLBACK_CATALOG_URL);
}
static naRef f_package_existingInstall( pkg::Package& pkg,
const nasal::CallContext& ctx )
{
return ctx.to_nasal(
pkg.existingInstall( ctx.getArg<pkg::Package::InstallCallback>(0) )
);
}
static naRef f_package_uninstall(pkg::Package& pkg, const nasal::CallContext& ctx)
{
pkg::InstallRef ins = pkg.existingInstall();
if (ins) {
ins->uninstall();
}
return naNil();
}
static SGPropertyNode_ptr queryPropsFromHash(const nasal::Hash& h)
{
SGPropertyNode_ptr props(new SGPropertyNode);
for (nasal::Hash::const_iterator it = h.begin(); it != h.end(); ++it) {
std::string const key = it->getKey();
if ((key == "name") || (key == "description")) {
props->setStringValue(key, it->getValue<std::string>());
} else if (strutils::starts_with(key, "rating-")) {
props->setIntValue(key, it->getValue<int>());
} else if (key == "tags") {
string_list tags = it->getValue<string_list>();
string_list::const_iterator tagIt;
int tagCount = 0;
for (tagIt = tags.begin(); tagIt != tags.end(); ++tagIt) {
SGPropertyNode_ptr tag = props->getChild("tag", tagCount++, true);
tag->setStringValue(*tagIt);
}
} else if (key == "installed") {
props->setBoolValue(key, it->getValue<bool>());
} else {
SG_LOG(SG_GENERAL, SG_WARN, "unknown filter term in hash:" << key);
}
}
return props;
}
static naRef f_root_search(pkg::Root& root, const nasal::CallContext& ctx)
{
SGPropertyNode_ptr query = queryPropsFromHash(ctx.requireArg<nasal::Hash>(0));
pkg::PackageList result = root.packagesMatching(query);
return ctx.to_nasal(result);
}
static naRef f_catalog_search(pkg::Catalog& cat, const nasal::CallContext& ctx)
{
SGPropertyNode_ptr query = queryPropsFromHash(ctx.requireArg<nasal::Hash>(0));
pkg::PackageList result = cat.packagesMatching(query);
return ctx.to_nasal(result);
}
static naRef f_catalog_packages(pkg::Catalog& cat, const nasal::CallContext& ctx)
{
// TODO: support package types
pkg::PackageList result = cat.packages();
return ctx.to_nasal(result);
}
static naRef f_catalog_installedPackages(pkg::Catalog& cat, naContext c)
{
// TODO: support package types
pkg::PackageList result = cat.installedPackages();
return nasal::to_nasal(c, result);
}
static naRef f_package_variants(pkg::Package& pack, naContext c)
{
nasal::Hash h(c);
string_list vars(pack.variants());
for (string_list_iterator it = vars.begin(); it != vars.end(); ++it) {
h.set(*it, pack.nameForVariant(*it));
}
return h.get_naRef();
}
void FGHTTPClient::postinit()
{
NasalPackageRoot::init("PackageRoot")
.member("path", &pkg::Root::path)
.member("version", &pkg::Root::catalogVersion)
.method("refresh", &pkg::Root::refresh)
.method("catalogs", &pkg::Root::catalogs)
.method("packageById", &pkg::Root::getPackageById)
.method("catalogById", &pkg::Root::getCatalogById)
.method("search", &f_root_search);
NasalCatalog::init("Catalog")
.member("installRoot", &pkg::Catalog::installRoot)
.member("id", &pkg::Catalog::id)
.member("url", &pkg::Catalog::url)
.member("description", &pkg::Catalog::description)
.method("packages", &f_catalog_packages)
.method("packageById", &pkg::Catalog::getPackageById)
.method("refresh", &pkg::Catalog::refresh)
.method("needingUpdate", &pkg::Catalog::packagesNeedingUpdate)
.member("installed", &f_catalog_installedPackages)
.method("search", &f_catalog_search)
.member("enabled", &pkg::Catalog::isEnabled);
NasalPackage::init("Package")
.member("id", &pkg::Package::id)
.member("name", &pkg::Package::name)
.member("description", &pkg::Package::description)
.member("installed", &pkg::Package::isInstalled)
.member("thumbnails", &pkg::Package::thumbnailUrls)
.member("variants", &f_package_variants)
.member("revision", &pkg::Package::revision)
.member("catalog", &pkg::Package::catalog)
.method("install", &pkg::Package::install)
.method("uninstall", &f_package_uninstall)
.method("existingInstall", &f_package_existingInstall)
.method("lprop", &pkg::Package::getLocalisedProp)
.member("fileSize", &pkg::Package::fileSizeBytes);
typedef pkg::Install* (pkg::Install::*InstallCallback)
(const pkg::Install::Callback&);
typedef pkg::Install* (pkg::Install::*ProgressCallback)
(const pkg::Install::ProgressCallback&);
NasalInstall::init("Install")
.member("revision", &pkg::Install::revsion)
.member("pkg", &pkg::Install::package)
.member("path", &pkg::Install::path)
.member("hasUpdate", &pkg::Install::hasUpdate)
.method("startUpdate", &pkg::Install::startUpdate)
.method("uninstall", &pkg::Install::uninstall)
.method("done", static_cast<InstallCallback>(&pkg::Install::done))
.method("fail", static_cast<InstallCallback>(&pkg::Install::fail))
.method("always", static_cast<InstallCallback>(&pkg::Install::always))
.method("progress", static_cast<ProgressCallback>(&pkg::Install::progress));
pkg::Root* packageRoot = globals->packageRoot();
if (packageRoot) {
FGNasalSys* nasalSys = globals->get_subsystem<FGNasalSys>();
nasal::Hash nasalGlobals = nasalSys->getGlobals();
nasal::Hash nasalPkg = nasalGlobals.createHash("pkg"); // module
nasalPkg.set("root", packageRoot);
}
}
void FGHTTPClient::shutdown()
{
_http.reset();
_inited = false;
}
void FGHTTPClient::update(double)
{
_http->update();
}
void FGHTTPClient::makeRequest(const simgear::HTTP::Request_ptr& req)
{
_http->makeRequest(req);
}
// Register the subsystem.
SGSubsystemMgr::Registrant<FGHTTPClient> registrantFGHTTPClient(
SGSubsystemMgr::GENERAL,
{{"nasal", SGSubsystemMgr::Dependency::HARD}});

View File

@@ -0,0 +1,67 @@
// HTTPClient.hxx -- Singleton HTTP client object
//
// Written by James Turner, started April 2012.
//
// Copyright (C) 2012 James Turner
//
// 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.
#ifndef FG_HTTP_CLIENT_HXX
#define FG_HTTP_CLIENT_HXX
#include <simgear/io/HTTPClient.hxx>
#include <simgear/package/Catalog.hxx>
#include <simgear/structure/subsystem_mgr.hxx>
#include <memory>
class FGHTTPClient : public SGSubsystem
{
public:
FGHTTPClient();
virtual ~FGHTTPClient();
// Subsystem API.
void init() override;
void postinit() override;
void shutdown() override;
void update(double) override;
// Subsystem identification.
static const char* staticSubsystemClassId() { return "http"; }
static FGHTTPClient* getOrCreate();
void makeRequest(const simgear::HTTP::Request_ptr& req);
simgear::HTTP::Client* client() { return _http.get(); }
simgear::HTTP::Client const* client() const { return _http.get(); }
bool isDefaultCatalogInstalled() const;
simgear::pkg::CatalogRef addDefaultCatalog();
std::string getDefaultCatalogId() const;
std::string getDefaultCatalogUrl() const;
std::string getDefaultCatalogFallbackUrl() const;
private:
bool _inited;
std::unique_ptr<simgear::HTTP::Client> _http;
};
#endif // FG_HTTP_CLIENT_HXX

View File

@@ -0,0 +1,92 @@
// RemoteXMLRequest.hxx -- xml http requests
//
// Written by James Turner
//
// Copyright (C) 2012 James Turner
//
// 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.
#ifndef REMOTEXMLREQUEST_HXXH
#define REMOTEXMLREQUEST_HXXH
#include <simgear/structure/exception.hxx>
#include <simgear/io/HTTPMemoryRequest.hxx>
#include <simgear/props/props_io.hxx>
class RemoteXMLRequest:
public simgear::HTTP::MemoryRequest
{
public:
SGPropertyNode_ptr _complete;
SGPropertyNode_ptr _status;
SGPropertyNode_ptr _failed;
SGPropertyNode_ptr _target;
RemoteXMLRequest(const std::string& url, SGPropertyNode* targetNode) :
simgear::HTTP::MemoryRequest(url),
_target(targetNode)
{
}
void setCompletionProp(SGPropertyNode_ptr p)
{
_complete = p;
}
void setStatusProp(SGPropertyNode_ptr p)
{
_status = p;
}
void setFailedProp(SGPropertyNode_ptr p)
{
_failed = p;
}
protected:
virtual void onFail()
{
SG_LOG(SG_IO, SG_INFO, "network level failure in RemoteXMLRequest");
if (_failed) {
_failed->setBoolValue(true);
}
}
virtual void onDone()
{
int response = responseCode();
bool failed = false;
if (response == 200) {
try {
const char* buffer = responseBody().c_str();
readProperties(buffer, responseBody().size(), _target, true);
} catch (const sg_exception &e) {
SG_LOG(SG_IO, SG_WARN, "parsing XML from remote, failed: " << e.getFormattedMessage());
failed = true;
response = 406; // 'not acceptable', anything better?
}
} else {
failed = true;
}
// now the response data is output, signal Nasal / listeners
if (_complete) _complete->setBoolValue(true);
if (_status) _status->setIntValue(response);
if (_failed && failed) _failed->setBoolValue(true);
}
};
#endif //REMOTEXMLREQUEST_HXXH

View File

@@ -0,0 +1,32 @@
include(FlightGearComponent)
set(SOURCES
swift_connection.cxx
dbusconnection.cpp
dbusobject.cpp
dbusmessage.cpp
dbusdispatcher.cpp
dbuserror.cpp
dbusserver.cpp
plugin.cpp
service.cpp
traffic.cpp
SwiftAircraftManager.cpp
)
set(HEADERS
swift_connection.hxx
dbusconnection.h
dbusobject.h
dbusmessage.h
dbuscallbacks.h
dbusdispatcher.h
dbuserror.h
dbusserver.h
plugin.h
service.h
traffic.h
SwiftAircraftManager.h
)
flightgear_component(Swift "${SOURCES}" "${HEADERS}")

View File

@@ -0,0 +1,129 @@
/*
* Manger class for aircraft generated by swift
* SPDX-FileCopyrightText: (C) 2019-2022 swift Project Community / Contributors (https://swift-project.org/)
* SPDX-FileCopyrightText: (C) 2019-2022 Lars Toenning <dev@ltoenning.de>
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "SwiftAircraftManager.h"
#include <Main/globals.hxx>
#include <utility>
FGSwiftAircraftManager::FGSwiftAircraftManager()
{
m_initialized = true;
}
FGSwiftAircraftManager::~FGSwiftAircraftManager()
{
this->removeAllPlanes();
m_initialized = false;
}
bool FGSwiftAircraftManager::isInitialized() const
{
return m_initialized;
}
bool FGSwiftAircraftManager::addPlane(const std::string& callsign, const std::string& modelString)
{
this->removePlane(callsign); // Remove plane if already exists e.g. when rematching is done.
auto curAircraft = FGAISwiftAircraftPtr(new FGAISwiftAircraft(callsign, modelString));
globals->get_subsystem<FGAIManager>()->attach(curAircraft);
// Init props after prop-root is assigned
curAircraft->initProps();
aircraftByCallsign.insert(std::pair<std::string, FGAISwiftAircraftPtr>(callsign, curAircraft));
return true;
}
void FGSwiftAircraftManager::updatePlanes(const std::vector<SwiftPlaneUpdate>& updates)
{
for (auto& update : updates) {
auto it = aircraftByCallsign.find(update.callsign);
if (it != aircraftByCallsign.end()) {
it->second->updatePosition(update.position, update.orientation, update.groundspeed, true);
}
}
}
void FGSwiftAircraftManager::getRemoteAircraftData(std::vector<std::string>& callsigns, std::vector<double>& latitudesDeg, std::vector<double>& longitudesDeg, std::vector<double>& elevationsM, std::vector<double>& verticalOffsets) const
{
if (callsigns.empty() || aircraftByCallsign.empty()) { return; }
const auto requestedCallsigns = callsigns;
callsigns.clear();
latitudesDeg.clear();
longitudesDeg.clear();
elevationsM.clear();
verticalOffsets.clear();
for (const auto& requestedCallsign : requestedCallsigns) {
const auto it = aircraftByCallsign.find(requestedCallsign);
if (it == aircraftByCallsign.end()) { continue; }
const FGAISwiftAircraft* aircraft = it->second;
assert(aircraft);
SGGeod pos;
pos.setLatitudeDeg(aircraft->_getLatitude());
pos.setLongitudeDeg(aircraft->_getLongitude());
const double latDeg = pos.getLatitudeDeg();
const double lonDeg = pos.getLongitudeDeg();
double groundElevation = aircraft->getGroundElevation(pos);
callsigns.push_back(requestedCallsign);
latitudesDeg.push_back(latDeg);
longitudesDeg.push_back(lonDeg);
elevationsM.push_back(groundElevation);
verticalOffsets.push_back(0);
}
}
void FGSwiftAircraftManager::removePlane(const std::string& callsign)
{
auto it = aircraftByCallsign.find(callsign);
if (it != aircraftByCallsign.end()) {
it->second->setDie(true);
aircraftByCallsign.erase(it);
}
}
void FGSwiftAircraftManager::removeAllPlanes()
{
for (auto it = aircraftByCallsign.begin(); it != aircraftByCallsign.end();) {
it->second->setDie(true);
it = aircraftByCallsign.erase(it);
}
}
double FGSwiftAircraftManager::getElevationAtPosition(const std::string& callsign, const SGGeod& pos) const
{
auto it = aircraftByCallsign.find(callsign);
if (it != aircraftByCallsign.end()) {
return it->second->getGroundElevation(pos);
}
// Aircraft not found in list
return std::numeric_limits<double>::quiet_NaN();
}
void FGSwiftAircraftManager::setPlanesTransponders(const std::vector<AircraftTransponder>& transponders)
{
for (const auto& transponder : transponders) {
auto it = aircraftByCallsign.find(transponder.callsign);
if (it != aircraftByCallsign.end()) {
it->second->setPlaneTransponder(transponder);
}
}
}
void FGSwiftAircraftManager::setPlanesSurfaces(const std::vector<AircraftSurfaces>& surfaces)
{
for (const auto& surface : surfaces) {
auto it = aircraftByCallsign.find(surface.callsign);
if (it != aircraftByCallsign.end()) {
it->second->setPlaneSurface(surface);
}
}
}

View File

@@ -0,0 +1,48 @@
/*
* Manger class for aircraft generated by swift
* SPDX-FileCopyrightText: (C) 2019-2022 swift Project Community / Contributors (https://swift-project.org/)
* SPDX-FileCopyrightText: (C) 2019-2022 Lars Toenning <dev@ltoenning.de>
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <AIModel/AIManager.hxx>
#include <AIModel/AISwiftAircraft.h>
#include <Scenery/scenery.hxx>
#include <unordered_map>
#include <vector>
#ifndef FGSWIFTAIRCRAFTMANAGER_H
#define FGSWIFTAIRCRAFTMANAGER_H
struct SwiftPlaneUpdate {
std::string callsign;
SGGeod position;
SGVec3d orientation;
double groundspeed;
bool onGround;
};
class FGSwiftAircraftManager
{
using FGAISwiftAircraftPtr = SGSharedPtr<FGAISwiftAircraft>;
public:
FGSwiftAircraftManager();
~FGSwiftAircraftManager();
bool addPlane(const std::string& callsign, const std::string& modelString);
void updatePlanes(const std::vector<SwiftPlaneUpdate>& updates);
void getRemoteAircraftData(std::vector<std::string>& callsigns, std::vector<double>& latitudesDeg, std::vector<double>& longitudesDeg,
std::vector<double>& elevationsM, std::vector<double>& verticalOffsets) const;
void removePlane(const std::string& callsign);
void removeAllPlanes();
void setPlanesTransponders(const std::vector<AircraftTransponder>& transponders);
double getElevationAtPosition(const std::string& callsign, const SGGeod& pos) const;
bool isInitialized() const;
void setPlanesSurfaces(const std::vector<AircraftSurfaces>& surfaces);
private:
std::unordered_map<std::string, FGAISwiftAircraftPtr> aircraftByCallsign;
bool m_initialized = false;
};
#endif

View File

@@ -0,0 +1,51 @@
/*
* SPDX-FileCopyrightText: (C) 2019-2022 swift Project Community / Contributors (https://swift-project.org/)
* SPDX-FileCopyrightText: (C) 2019-2022 Lars Toenning <dev@ltoenning.de>
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef BLACKSIM_FGSWIFTBUS_DBUSASYNCCALLBACKS_H
#define BLACKSIM_FGSWIFTBUS_DBUSASYNCCALLBACKS_H
#include <dbus/dbus.h>
#include <functional>
namespace FGSwiftBus {
//! \cond PRIVATE
template <typename T>
class DBusAsyncCallbacks
{
public:
DBusAsyncCallbacks() = default;
DBusAsyncCallbacks(const std::function<dbus_bool_t(T*)>& add,
const std::function<void(T*)>& remove,
const std::function<void(T*)>& toggled)
: m_addHandler(add), m_removeHandler(remove), m_toggledHandler(toggled)
{
}
static dbus_bool_t add(T* watch, void* refcon)
{
return static_cast<DBusAsyncCallbacks*>(refcon)->m_addHandler(watch);
}
static void remove(T* watch, void* refcon)
{
return static_cast<DBusAsyncCallbacks*>(refcon)->m_removeHandler(watch);
}
static void toggled(T* watch, void* refcon)
{
return static_cast<DBusAsyncCallbacks*>(refcon)->m_toggledHandler(watch);
}
private:
std::function<dbus_bool_t(T*)> m_addHandler;
std::function<void(T*)> m_removeHandler;
std::function<void(T*)> m_toggledHandler;
};
//! \endcond
} // namespace FGSwiftBus
#endif // guard

View File

@@ -0,0 +1,174 @@
/*
* SPDX-FileCopyrightText: (C) 2019-2022 swift Project Community / Contributors (https://swift-project.org/)
* SPDX-FileCopyrightText: (C) 2019-2022 Lars Toenning <dev@ltoenning.de>
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "dbusconnection.h"
#include "dbusobject.h"
#include <algorithm>
#include <cassert>
#include <memory>
namespace FGSwiftBus {
CDBusConnection::CDBusConnection()
{
dbus_threads_init_default();
}
CDBusConnection::CDBusConnection(DBusConnection* connection)
{
m_connection.reset(connection);
dbus_connection_ref(connection);
// Don't exit application, if the connection is disconnected
dbus_connection_set_exit_on_disconnect(connection, false);
dbus_connection_add_filter(connection, filterDisconnectedFunction, this, nullptr);
}
CDBusConnection::~CDBusConnection()
{
close();
if (m_connection) { dispatch(); } // dispatch is virtual, but safe to call in dtor, as it's declared final
if (m_dispatcher) { m_dispatcher->remove(this); }
}
bool CDBusConnection::connect(BusType type)
{
assert(type == SessionBus);
DBusError error;
dbus_error_init(&error);
DBusBusType dbusBusType;
switch (type) {
case SessionBus: dbusBusType = DBUS_BUS_SESSION; break;
}
m_connection.reset(dbus_bus_get_private(dbusBusType, &error));
if (dbus_error_is_set(&error)) {
m_lastError = CDBusError(&error);
return false;
}
// Don't exit application, if the connection is disconnected
dbus_connection_set_exit_on_disconnect(m_connection.get(), false);
return true;
}
void CDBusConnection::setDispatcher(CDBusDispatcher* dispatcher)
{
assert(dispatcher);
m_dispatcher = dispatcher;
m_dispatcher->add(this);
dbus_connection_set_watch_functions(
m_connection.get(),
dispatcher->m_watchCallbacks.add,
dispatcher->m_watchCallbacks.remove,
dispatcher->m_watchCallbacks.toggled,
&dispatcher->m_watchCallbacks, nullptr);
dbus_connection_set_timeout_functions(
m_connection.get(),
dispatcher->m_timeoutCallbacks.add,
dispatcher->m_timeoutCallbacks.remove,
dispatcher->m_timeoutCallbacks.toggled,
&dispatcher->m_timeoutCallbacks, nullptr);
}
void CDBusConnection::requestName(const std::string& name)
{
DBusError error;
dbus_error_init(&error);
dbus_bus_request_name(m_connection.get(), name.c_str(), 0, &error);
}
bool CDBusConnection::isConnected() const
{
return m_connection && dbus_connection_get_is_connected(m_connection.get());
}
void CDBusConnection::registerDisconnectedCallback(CDBusObject* obj, DisconnectedCallback func)
{
m_disconnectedCallbacks[obj] = func;
}
void CDBusConnection::unregisterDisconnectedCallback(CDBusObject* obj)
{
auto it = m_disconnectedCallbacks.find(obj);
if (it == m_disconnectedCallbacks.end()) { return; }
m_disconnectedCallbacks.erase(it);
}
void CDBusConnection::registerObjectPath(CDBusObject* object, const std::string& interfaceName, const std::string& objectPath, const DBusObjectPathVTable& dbusObjectPathVTable)
{
(void)interfaceName;
if (!m_connection) { return; }
dbus_connection_try_register_object_path(m_connection.get(), objectPath.c_str(), &dbusObjectPathVTable, object, nullptr);
}
void CDBusConnection::sendMessage(const CDBusMessage& message)
{
if (!isConnected()) { return; }
dbus_uint32_t serial = message.getSerial();
dbus_connection_send(m_connection.get(), message.m_message, &serial);
}
void CDBusConnection::close()
{
if (m_connection) { dbus_connection_close(m_connection.get()); }
}
void CDBusConnection::dispatch()
{
dbus_connection_ref(m_connection.get());
if (dbus_connection_get_dispatch_status(m_connection.get()) == DBUS_DISPATCH_DATA_REMAINS) {
while (dbus_connection_dispatch(m_connection.get()) == DBUS_DISPATCH_DATA_REMAINS)
;
}
dbus_connection_unref(m_connection.get());
}
void CDBusConnection::setDispatchStatus(DBusConnection* connection, DBusDispatchStatus status)
{
if (dbus_connection_get_is_connected(connection) == FALSE) { return; }
switch (status) {
case DBUS_DISPATCH_DATA_REMAINS:
//m_dispatcher->add(this);
break;
case DBUS_DISPATCH_COMPLETE:
case DBUS_DISPATCH_NEED_MEMORY:
break;
}
}
void CDBusConnection::setDispatchStatus(DBusConnection* connection, DBusDispatchStatus status, void* data)
{
auto* obj = static_cast<CDBusConnection*>(data);
obj->setDispatchStatus(connection, status);
}
DBusHandlerResult CDBusConnection::filterDisconnectedFunction(DBusConnection* connection, DBusMessage* message, void* data)
{
(void)connection; // unused
auto* obj = static_cast<CDBusConnection*>(data);
DBusError err;
dbus_error_init(&err);
if (dbus_message_is_signal(message, DBUS_INTERFACE_LOCAL, "Disconnected")) {
for (auto it = obj->m_disconnectedCallbacks.begin(); it != obj->m_disconnectedCallbacks.end(); ++it) {
it->second();
}
return DBUS_HANDLER_RESULT_HANDLED;
}
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
} // namespace FGSwiftBus

View File

@@ -0,0 +1,104 @@
/*
* SPDX-FileCopyrightText: (C) 2019-2022 swift Project Community / Contributors (https://swift-project.org/)
* SPDX-FileCopyrightText: (C) 2019-2022 Lars Toenning <dev@ltoenning.de>
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef BLACKSIM_FGSWIFTBUS_DBUSCONNECTION_H
#define BLACKSIM_FGSWIFTBUS_DBUSCONNECTION_H
#include "dbuscallbacks.h"
#include "dbusdispatcher.h"
#include "dbuserror.h"
#include "dbusmessage.h"
#include <dbus/dbus.h>
#include <event2/event.h>
#include <memory>
#include <string>
#include <unordered_map>
namespace FGSwiftBus {
class CDBusObject;
//! DBus connection
class CDBusConnection : public IDispatchable
{
public:
//! Bus type
enum BusType { SessionBus };
//! Disconnect Callback
using DisconnectedCallback = std::function<void()>;
//! Default constructor
CDBusConnection();
//! Constructor
CDBusConnection(DBusConnection* connection);
//! Destructor
~CDBusConnection() override;
// The ones below are not implemented yet.
// If you need them, make sure that connection reference count is correct
CDBusConnection(const CDBusConnection&) = delete;
CDBusConnection& operator=(const CDBusConnection&) = delete;
//! Connect to bus
bool connect(BusType type);
//! Set dispatcher
void setDispatcher(CDBusDispatcher* dispatcher);
//! Request name to the bus
void requestName(const std::string& name);
//! Is connected?
bool isConnected() const;
//! Register a disconnected callback
void registerDisconnectedCallback(CDBusObject* obj, DisconnectedCallback func);
//! Register a disconnected callback
void unregisterDisconnectedCallback(CDBusObject* obj);
//! Register DBus object with interfaceName and objectPath.
//! \param object
//! \param interfaceName
//! \param objectPath
//! \param dbusObjectPathVTable Virtual table handling DBus messages
void registerObjectPath(CDBusObject* object, const std::string& interfaceName, const std::string& objectPath, const DBusObjectPathVTable& dbusObjectPathVTable);
//! Send message to bus
void sendMessage(const CDBusMessage& message);
//! Close connection
void close();
//! Get the last error
CDBusError lastError() const { return m_lastError; }
protected:
// cppcheck-suppress virtualCallInConstructor
virtual void dispatch() override final;
private:
void setDispatchStatus(DBusConnection* connection, DBusDispatchStatus status);
static void setDispatchStatus(DBusConnection* connection, DBusDispatchStatus status, void* data);
static DBusHandlerResult filterDisconnectedFunction(DBusConnection* connection, DBusMessage* message, void* data);
struct DBusConnectionDeleter {
void operator()(DBusConnection* obj) const { dbus_connection_unref(obj); }
};
CDBusDispatcher* m_dispatcher = nullptr;
std::unique_ptr<DBusConnection, DBusConnectionDeleter> m_connection;
CDBusError m_lastError;
std::unordered_map<CDBusObject*, DisconnectedCallback> m_disconnectedCallbacks;
};
} // namespace FGSwiftBus
#endif // guard

View File

@@ -0,0 +1,242 @@
/*
* SPDX-FileCopyrightText: (C) 2019-2022 swift Project Community / Contributors (https://swift-project.org/)
* SPDX-FileCopyrightText: (C) 2019-2022 Lars Toenning <dev@ltoenning.de>
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "dbusdispatcher.h"
#include "dbusconnection.h"
#include <algorithm>
namespace { // anonymosu namespace
template <typename T, typename... Args>
std::unique_ptr<T> our_make_unique(Args&&... args)
{
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
} // end of anonymous namespace
namespace FGSwiftBus {
//! Functor struct deleteing an event
struct EventDeleter {
//! Delete functor
void operator()(event* obj) const
{
event_del(obj);
event_free(obj);
}
};
//! DBus watch handler
class WatchHandler
{
public:
//! Constructor
WatchHandler(event_base* base, DBusWatch* watch)
: m_base(base), m_watch(watch)
{
const unsigned int flags = dbus_watch_get_flags(watch);
short monitoredEvents = EV_PERSIST;
if (flags & DBUS_WATCH_READABLE) { monitoredEvents |= EV_READ; }
if (flags & DBUS_WATCH_WRITABLE) { monitoredEvents |= EV_WRITE; }
const int fd = dbus_watch_get_unix_fd(watch);
m_event.reset(event_new(m_base, fd, monitoredEvents, callback, this));
event_add(m_event.get(), nullptr);
}
//! Get DBus watch
DBusWatch* getWatch() { return m_watch; }
//! Get DBus watch
const DBusWatch* getWatch() const { return m_watch; }
private:
//! Event callback
static void callback(evutil_socket_t fd, short event, void* data)
{
(void)fd; // Not really unused, but GCC/Clang still complain about it.
auto* watchHandler = static_cast<WatchHandler*>(data);
unsigned int flags = 0;
if (event & EV_READ) { flags |= DBUS_WATCH_READABLE; }
if (event & EV_WRITE) { flags |= DBUS_WATCH_WRITABLE; }
dbus_watch_handle(watchHandler->m_watch, flags);
}
event_base* m_base = nullptr;
std::unique_ptr<event, EventDeleter> m_event;
DBusWatch* m_watch = nullptr;
};
//! DBus timeout handler
class TimeoutHandler
{
public:
//! Constructor
TimeoutHandler(event_base* base, DBusTimeout* timeout)
: m_base(base), m_timeout(timeout)
{
timeval timer;
const int interval = dbus_timeout_get_interval(timeout);
timer.tv_sec = interval / 1000;
timer.tv_usec = (interval % 1000) * 1000;
m_event.reset(evtimer_new(m_base, callback, this));
evtimer_add(m_event.get(), &timer);
}
//! Get DBus timeout
const DBusTimeout* getTimeout() const { return m_timeout; }
private:
//! Event callback
static void callback(evutil_socket_t fd, short event, void* data)
{
(void)fd; // unused
(void)event; // unused
auto* timeoutHandler = static_cast<TimeoutHandler*>(data);
dbus_timeout_handle(timeoutHandler->m_timeout);
}
event_base* m_base = nullptr;
std::unique_ptr<event, EventDeleter> m_event;
DBusTimeout* m_timeout = nullptr;
};
//! Generic Timer
class Timer
{
public:
Timer() = default;
//! Constructor
Timer(event_base* base, const timeval& timeout, const std::function<void()>& func)
: m_base(base), m_func(func)
{
m_event.reset(evtimer_new(m_base, callback, this));
evtimer_add(m_event.get(), &timeout);
}
private:
//! Event callback
static void callback(evutil_socket_t fd, short event, void* data)
{
(void)fd; // unused
(void)event; // unused
auto* timer = static_cast<Timer*>(data);
timer->m_func();
delete timer;
}
event_base* m_base = nullptr;
std::unique_ptr<event, EventDeleter> m_event;
std::function<void()> m_func;
};
CDBusDispatcher::CDBusDispatcher() : m_eventBase(event_base_new())
{
using namespace std::placeholders;
m_watchCallbacks = WatchCallbacks(std::bind(&CDBusDispatcher::dbusAddWatch, this, _1),
std::bind(&CDBusDispatcher::dbusRemoveWatch, this, _1),
std::bind(&CDBusDispatcher::dbusWatchToggled, this, _1));
m_timeoutCallbacks = TimeoutCallbacks(std::bind(&CDBusDispatcher::dbusAddTimeout, this, _1),
std::bind(&CDBusDispatcher::dbusRemoveTimeout, this, _1),
std::bind(&CDBusDispatcher::dbusTimeoutToggled, this, _1));
}
CDBusDispatcher::~CDBusDispatcher()
{
}
void CDBusDispatcher::add(IDispatchable* dispatchable)
{
m_dispatchList.push_back(dispatchable);
}
void CDBusDispatcher::remove(IDispatchable* dispatchable)
{
auto it = std::find(m_dispatchList.begin(), m_dispatchList.end(), dispatchable);
if (it != m_dispatchList.end()) { m_dispatchList.erase(it); }
}
void CDBusDispatcher::waitAndRun()
{
if (!m_eventBase) { return; }
event_base_dispatch(m_eventBase.get());
}
void CDBusDispatcher::runOnce()
{
if (!m_eventBase) { return; }
event_base_loop(m_eventBase.get(), EVLOOP_NONBLOCK);
dispatch();
}
void CDBusDispatcher::dispatch()
{
if (m_dispatchList.empty()) { return; }
for (IDispatchable* dispatchable : m_dispatchList) {
dispatchable->dispatch();
}
}
dbus_bool_t CDBusDispatcher::dbusAddWatch(DBusWatch* watch)
{
if (dbus_watch_get_enabled(watch) == FALSE) { return true; }
int fd = dbus_watch_get_unix_fd(watch);
m_watchers.emplace(fd, our_make_unique<WatchHandler>(m_eventBase.get(), watch));
return true;
}
void CDBusDispatcher::dbusRemoveWatch(DBusWatch* watch)
{
for (auto it = m_watchers.begin(); it != m_watchers.end();) {
if (it->second->getWatch() == watch) {
it = m_watchers.erase(it);
} else {
++it;
}
}
}
void CDBusDispatcher::dbusWatchToggled(DBusWatch* watch)
{
if (dbus_watch_get_enabled(watch) == TRUE) {
dbusAddWatch(watch);
} else {
dbusRemoveWatch(watch);
}
}
dbus_bool_t CDBusDispatcher::dbusAddTimeout(DBusTimeout* timeout)
{
if (dbus_timeout_get_enabled(timeout) == FALSE) { return TRUE; }
m_timeouts.emplace_back(new TimeoutHandler(m_eventBase.get(), timeout));
return true;
}
void CDBusDispatcher::dbusRemoveTimeout(DBusTimeout* timeout)
{
auto predicate = [timeout](const std::unique_ptr<TimeoutHandler>& ptr) {
return ptr->getTimeout() == timeout;
};
m_timeouts.erase(std::remove_if(m_timeouts.begin(), m_timeouts.end(), predicate), m_timeouts.end());
}
void CDBusDispatcher::dbusTimeoutToggled(DBusTimeout* timeout)
{
if (dbus_timeout_get_enabled(timeout) == TRUE)
dbusAddTimeout(timeout);
else
dbusRemoveTimeout(timeout);
}
} // namespace FGSwiftBus

View File

@@ -0,0 +1,99 @@
/*
* SPDX-FileCopyrightText: (C) 2019-2022 swift Project Community / Contributors (https://swift-project.org/)
* SPDX-FileCopyrightText: (C) 2019-2022 Lars Toenning <dev@ltoenning.de>
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef BLACKSIM_FGSWIFTBUS_DBUSDISPATCHER_H
#define BLACKSIM_FGSWIFTBUS_DBUSDISPATCHER_H
#include "dbuscallbacks.h"
#include <dbus/dbus.h>
#include <event2/event.h>
#include <memory>
#include <unordered_map>
#include <vector>
namespace FGSwiftBus {
class WatchHandler;
class TimeoutHandler;
class CDBusConnection;
class CDBusDispatcher;
//! Dispatchable Interface
class IDispatchable
{
public:
//! Default constructor
IDispatchable() = default;
//! Default destructor
virtual ~IDispatchable() = default;
//! Dispatch execution method
virtual void dispatch() = 0;
private:
friend CDBusDispatcher;
};
//! DBus Dispatcher
class CDBusDispatcher
{
public:
//! Constructor
CDBusDispatcher();
//! Destructor
virtual ~CDBusDispatcher();
//! Add dispatchable object
void add(IDispatchable* dispatchable);
//! Remove dispatchable object
void remove(IDispatchable* dispatchable);
//! Waits for events to be dispatched and handles them
void waitAndRun();
//! Dispatches ready handlers and returns without waiting
void runOnce();
private:
friend class WatchHandler;
friend class TimeoutHandler;
friend class Timer;
friend class CDBusConnection;
friend class CDBusServer;
struct EventBaseDeleter {
void operator()(event_base* obj) const { event_base_free(obj); }
};
using WatchCallbacks = DBusAsyncCallbacks<DBusWatch>;
using TimeoutCallbacks = DBusAsyncCallbacks<DBusTimeout>;
void dispatch();
dbus_bool_t dbusAddWatch(DBusWatch* watch);
void dbusRemoveWatch(DBusWatch* watch);
void dbusWatchToggled(DBusWatch* watch);
dbus_bool_t dbusAddTimeout(DBusTimeout* timeout);
void dbusRemoveTimeout(DBusTimeout* timeout);
void dbusTimeoutToggled(DBusTimeout* timeout);
WatchCallbacks m_watchCallbacks;
TimeoutCallbacks m_timeoutCallbacks;
std::unordered_multimap<evutil_socket_t, std::unique_ptr<WatchHandler>> m_watchers;
std::vector<std::unique_ptr<TimeoutHandler>> m_timeouts;
std::unique_ptr<event_base, EventBaseDeleter> m_eventBase;
std::vector<IDispatchable*> m_dispatchList;
};
} // namespace FGSwiftBus
#endif

View File

@@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: (C) 2019-2022 swift Project Community / Contributors (https://swift-project.org/)
* SPDX-FileCopyrightText: (C) 2019-2022 Lars Toenning <dev@ltoenning.de>
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "dbuserror.h"
namespace FGSwiftBus {
CDBusError::CDBusError(const DBusError* error)
: m_name(error->name), m_message(error->message)
{
}
} // namespace FGSwiftBus

View File

@@ -0,0 +1,42 @@
/*
* SPDX-FileCopyrightText: (C) 2019-2022 swift Project Community / Contributors (https://swift-project.org/)
* SPDX-FileCopyrightText: (C) 2019-2022 Lars Toenning <dev@ltoenning.de>
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef BLACKSIM_FGSWIFTBUS_DBUSERROR_H
#define BLACKSIM_FGSWIFTBUS_DBUSERROR_H
#include <dbus/dbus.h>
#include <string>
namespace FGSwiftBus {
//! DBus error
class CDBusError
{
public:
//! Error type
enum ErrorType {
NoError,
Other
};
//! Default constructur
CDBusError() = default;
//! Constructor
explicit CDBusError(const DBusError* error);
//! Get error type
ErrorType getType() const { return m_errorType; }
private:
ErrorType m_errorType = NoError;
std::string m_name;
std::string m_message;
};
} // namespace FGSwiftBus
#endif // guard

View File

@@ -0,0 +1,246 @@
/*
* SPDX-FileCopyrightText: (C) 2019-2022 swift Project Community / Contributors (https://swift-project.org/)
* SPDX-FileCopyrightText: (C) 2019-2022 Lars Toenning <dev@ltoenning.de>
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "dbusmessage.h"
namespace FGSwiftBus {
CDBusMessage::CDBusMessage(DBusMessage* message)
{
m_message = dbus_message_ref(message);
}
CDBusMessage::CDBusMessage(const CDBusMessage& other)
{
m_message = dbus_message_ref(other.m_message);
m_serial = other.m_serial;
}
CDBusMessage::CDBusMessage(DBusMessage* message, dbus_uint32_t serial)
{
m_message = dbus_message_ref(message);
m_serial = serial;
}
CDBusMessage::~CDBusMessage()
{
dbus_message_unref(m_message);
}
CDBusMessage& CDBusMessage::operator=(CDBusMessage other)
{
std::swap(m_serial, other.m_serial);
m_message = dbus_message_ref(other.m_message);
return *this;
}
bool CDBusMessage::isMethodCall() const
{
return dbus_message_get_type(m_message) == DBUS_MESSAGE_TYPE_METHOD_CALL;
}
bool CDBusMessage::wantsReply() const
{
return !dbus_message_get_no_reply(m_message);
}
std::string CDBusMessage::getSender() const
{
const char* sender = dbus_message_get_sender(m_message);
return sender ? std::string(sender) : std::string();
}
dbus_uint32_t CDBusMessage::getSerial() const
{
return dbus_message_get_serial(m_message);
}
std::string CDBusMessage::getInterfaceName() const
{
return dbus_message_get_interface(m_message);
}
std::string CDBusMessage::getObjectPath() const
{
return dbus_message_get_path(m_message);
}
std::string CDBusMessage::getMethodName() const
{
return dbus_message_get_member(m_message);
}
void CDBusMessage::beginArgumentWrite()
{
dbus_message_iter_init_append(m_message, &m_messageIterator);
}
void CDBusMessage::appendArgument(bool value)
{
dbus_bool_t boolean = value ? 1 : 0;
dbus_message_iter_append_basic(&m_messageIterator, DBUS_TYPE_BOOLEAN, &boolean);
}
void CDBusMessage::appendArgument(const char* value)
{
dbus_message_iter_append_basic(&m_messageIterator, DBUS_TYPE_STRING, &value);
}
void CDBusMessage::appendArgument(const std::string& value)
{
const char* ptr = value.c_str();
dbus_message_iter_append_basic(&m_messageIterator, DBUS_TYPE_STRING, &ptr);
}
void CDBusMessage::appendArgument(int value)
{
dbus_int32_t i = value;
dbus_message_iter_append_basic(&m_messageIterator, DBUS_TYPE_INT32, &i);
}
void CDBusMessage::appendArgument(double value)
{
dbus_message_iter_append_basic(&m_messageIterator, DBUS_TYPE_DOUBLE, &value);
}
void CDBusMessage::appendArgument(const std::vector<double>& array)
{
DBusMessageIter arrayIterator;
dbus_message_iter_open_container(&m_messageIterator, DBUS_TYPE_ARRAY, DBUS_TYPE_DOUBLE_AS_STRING, &arrayIterator);
const double* ptr = array.data();
dbus_message_iter_append_fixed_array(&arrayIterator, DBUS_TYPE_DOUBLE, &ptr, static_cast<int>(array.size()));
dbus_message_iter_close_container(&m_messageIterator, &arrayIterator);
}
void CDBusMessage::appendArgument(const std::vector<std::string>& array)
{
DBusMessageIter arrayIterator;
dbus_message_iter_open_container(&m_messageIterator, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING_AS_STRING, &arrayIterator);
for (const auto& i : array) {
const char* ptr = i.c_str();
dbus_message_iter_append_basic(&arrayIterator, DBUS_TYPE_STRING, &ptr);
}
dbus_message_iter_close_container(&m_messageIterator, &arrayIterator);
}
void CDBusMessage::beginArgumentRead()
{
dbus_message_iter_init(m_message, &m_messageIterator);
}
void CDBusMessage::getArgument(int& value)
{
if (dbus_message_iter_get_arg_type(&m_messageIterator) != DBUS_TYPE_INT32) { return; }
dbus_int32_t i;
dbus_message_iter_get_basic(&m_messageIterator, &i);
value = i;
dbus_message_iter_next(&m_messageIterator);
}
void CDBusMessage::getArgument(bool& value)
{
if (dbus_message_iter_get_arg_type(&m_messageIterator) != DBUS_TYPE_BOOLEAN) { return; }
dbus_bool_t v;
dbus_message_iter_get_basic(&m_messageIterator, &v);
if (v == TRUE) {
value = true;
} else {
value = false;
}
dbus_message_iter_next(&m_messageIterator);
}
void CDBusMessage::getArgument(double& value)
{
if (dbus_message_iter_get_arg_type(&m_messageIterator) != DBUS_TYPE_DOUBLE) { return; }
dbus_message_iter_get_basic(&m_messageIterator, &value);
dbus_message_iter_next(&m_messageIterator);
}
void CDBusMessage::getArgument(std::string& value)
{
const char* str = nullptr;
if (dbus_message_iter_get_arg_type(&m_messageIterator) != DBUS_TYPE_STRING) { return; }
dbus_message_iter_get_basic(&m_messageIterator, &str);
dbus_message_iter_next(&m_messageIterator);
value = std::string(str);
}
void CDBusMessage::getArgument(std::vector<int>& value)
{
DBusMessageIter arrayIterator;
dbus_message_iter_recurse(&m_messageIterator, &arrayIterator);
do {
if (dbus_message_iter_get_arg_type(&arrayIterator) != DBUS_TYPE_INT32) { return; }
dbus_int32_t i;
dbus_message_iter_get_basic(&arrayIterator, &i);
value.push_back(i);
} while (dbus_message_iter_next(&arrayIterator));
dbus_message_iter_next(&m_messageIterator);
}
void CDBusMessage::getArgument(std::vector<bool>& value)
{
if (dbus_message_iter_get_arg_type(&m_messageIterator) != DBUS_TYPE_ARRAY) { return; }
DBusMessageIter arrayIterator;
dbus_message_iter_recurse(&m_messageIterator, &arrayIterator);
do {
if (dbus_message_iter_get_arg_type(&arrayIterator) != DBUS_TYPE_BOOLEAN) { return; }
dbus_bool_t b;
dbus_message_iter_get_basic(&arrayIterator, &b);
if (b == TRUE) {
value.push_back(true);
} else {
value.push_back(false);
}
} while (dbus_message_iter_next(&arrayIterator));
dbus_message_iter_next(&m_messageIterator);
}
void CDBusMessage::getArgument(std::vector<double>& value)
{
DBusMessageIter arrayIterator;
dbus_message_iter_recurse(&m_messageIterator, &arrayIterator);
do {
if (dbus_message_iter_get_arg_type(&arrayIterator) != DBUS_TYPE_DOUBLE) { return; }
double d;
dbus_message_iter_get_basic(&arrayIterator, &d);
value.push_back(d);
} while (dbus_message_iter_next(&arrayIterator));
dbus_message_iter_next(&m_messageIterator);
}
void CDBusMessage::getArgument(std::vector<std::string>& value)
{
DBusMessageIter arrayIterator;
dbus_message_iter_recurse(&m_messageIterator, &arrayIterator);
do {
if (dbus_message_iter_get_arg_type(&arrayIterator) != DBUS_TYPE_STRING) { return; }
const char* str = nullptr;
dbus_message_iter_get_basic(&arrayIterator, &str);
value.push_back(std::string(str));
} while (dbus_message_iter_next(&arrayIterator));
dbus_message_iter_next(&m_messageIterator);
}
CDBusMessage CDBusMessage::createSignal(const std::string& path, const std::string& interfaceName, const std::string& signalName)
{
DBusMessage* signal = dbus_message_new_signal(path.c_str(), interfaceName.c_str(), signalName.c_str());
return CDBusMessage(signal);
}
CDBusMessage CDBusMessage::createReply(const std::string& destination, dbus_uint32_t serial)
{
DBusMessage* reply = dbus_message_new(DBUS_MESSAGE_TYPE_METHOD_RETURN);
dbus_message_set_no_reply(reply, TRUE);
if (!destination.empty()) { dbus_message_set_destination(reply, destination.c_str()); }
dbus_message_set_reply_serial(reply, serial);
CDBusMessage msg(reply);
dbus_message_unref(reply);
return msg;
}
} // namespace FGSwiftBus

View File

@@ -0,0 +1,99 @@
/*
* SPDX-FileCopyrightText: (C) 2019-2022 swift Project Community / Contributors (https://swift-project.org/)
* SPDX-FileCopyrightText: (C) 2019-2022 Lars Toenning <dev@ltoenning.de>
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef BLACKSIM_FGSWIFTBUS_DBUSMESSAGE_H
#define BLACKSIM_FGSWIFTBUS_DBUSMESSAGE_H
#include "dbus/dbus.h"
#include <string>
#include <vector>
namespace FGSwiftBus {
//! DBus Message
class CDBusMessage
{
public:
//! Constructor
//! @{
CDBusMessage(DBusMessage* message);
CDBusMessage(const CDBusMessage& other);
//! @}
//! Destructor
~CDBusMessage();
//! Assignment operator
CDBusMessage& operator=(CDBusMessage other);
//! Is this message a method call?
bool isMethodCall() const;
//! Does this message want a reply?
bool wantsReply() const;
//! Get the message sender
std::string getSender() const;
//! Get the message serial. This is usally required for reply message.
dbus_uint32_t getSerial() const;
//! Get the called interface name
std::string getInterfaceName() const;
//! Get the called object path
std::string getObjectPath() const;
//! Get the called method name
std::string getMethodName() const;
//! Begin writing argument
void beginArgumentWrite();
//! Append argument. Make sure to call \sa beginArgumentWrite() before.
//! @{
void appendArgument(bool value);
void appendArgument(const char* value);
void appendArgument(const std::string& value);
void appendArgument(int value);
void appendArgument(double value);
void appendArgument(const std::vector<double>& array);
void appendArgument(const std::vector<std::string>& array);
//! @}
//! Begin reading arguments
void beginArgumentRead();
//! Read single argument. Make sure to call \sa beginArgumentRead() before.
//! @{
void getArgument(int& value);
void getArgument(bool& value);
void getArgument(double& value);
void getArgument(std::string& value);
void getArgument(std::vector<int>& value);
void getArgument(std::vector<bool>& value);
void getArgument(std::vector<double>& value);
void getArgument(std::vector<std::string>& value);
//! @}
//! Creates a DBus message containing a DBus signal
static CDBusMessage createSignal(const std::string& path, const std::string& interfaceName, const std::string& signalName);
//! Creates a DBus message containing a DBus reply
static CDBusMessage createReply(const std::string& destination, dbus_uint32_t serial);
private:
friend class CDBusConnection;
DBusMessage* m_message = nullptr;
DBusMessageIter m_messageIterator;
CDBusMessage(DBusMessage* message, dbus_uint32_t serial);
dbus_uint32_t m_serial = 0;
};
} // namespace FGSwiftBus
#endif // guard

View File

@@ -0,0 +1,91 @@
/*
* SPDX-FileCopyrightText: (C) 2019-2022 swift Project Community / Contributors (https://swift-project.org/)
* SPDX-FileCopyrightText: (C) 2019-2022 Lars Toenning <dev@ltoenning.de>
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "dbusobject.h"
#include <cassert>
namespace FGSwiftBus {
CDBusObject::CDBusObject()
{
}
CDBusObject::~CDBusObject()
{
if (m_dbusConnection) { m_dbusConnection->unregisterDisconnectedCallback(this); }
};
void CDBusObject::setDBusConnection(const std::shared_ptr<CDBusConnection>& dbusConnection)
{
m_dbusConnection = dbusConnection;
dbusConnectedHandler();
CDBusConnection::DisconnectedCallback disconnectedHandler = std::bind(&CDBusObject::dbusDisconnectedHandler, this);
m_dbusConnection->registerDisconnectedCallback(this, disconnectedHandler);
}
void CDBusObject::registerDBusObjectPath(const std::string& interfaceName, const std::string& objectPath)
{
assert(m_dbusConnection);
m_interfaceName = interfaceName;
m_objectPath = objectPath;
m_dbusConnection->registerObjectPath(this, interfaceName, objectPath, m_dbusObjectPathVTable);
}
void CDBusObject::sendDBusSignal(const std::string& name)
{
if (!m_dbusConnection) { return; }
CDBusMessage signal = CDBusMessage::createSignal(m_objectPath, m_interfaceName, name);
m_dbusConnection->sendMessage(signal);
}
void CDBusObject::sendDBusMessage(const CDBusMessage& message)
{
if (!m_dbusConnection) { return; }
m_dbusConnection->sendMessage(message);
}
void CDBusObject::maybeSendEmptyDBusReply(bool wantsReply, const std::string& destination, dbus_uint32_t serial)
{
if (wantsReply) {
CDBusMessage reply = CDBusMessage::createReply(destination, serial);
m_dbusConnection->sendMessage(reply);
}
}
void CDBusObject::queueDBusCall(const std::function<void()>& func)
{
std::lock_guard<std::mutex> lock(m_mutex);
m_qeuedDBusCalls.push_back(func);
}
void CDBusObject::invokeQueuedDBusCalls()
{
std::lock_guard<std::mutex> lock(m_mutex);
while (m_qeuedDBusCalls.size() > 0) {
m_qeuedDBusCalls.front()();
m_qeuedDBusCalls.pop_front();
}
}
void CDBusObject::dbusObjectPathUnregisterFunction(DBusConnection* connection, void* data)
{
(void)connection; // unused
(void)data; // unused
}
DBusHandlerResult CDBusObject::dbusObjectPathMessageFunction(DBusConnection* connection, DBusMessage* message, void* data)
{
(void)connection; // unused
auto* obj = static_cast<CDBusObject*>(data);
DBusError err;
dbus_error_init(&err);
CDBusMessage dbusMessage(message);
return obj->dbusMessageHandler(dbusMessage);
}
} // namespace FGSwiftBus

View File

@@ -0,0 +1,94 @@
/*
* SPDX-FileCopyrightText: (C) 2019-2022 swift Project Community / Contributors (https://swift-project.org/)
* SPDX-FileCopyrightText: (C) 2019-2022 Lars Toenning <dev@ltoenning.de>
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef BLACKSIM_FGSWIFTBUS_DBUSOBJECT_H
#define BLACKSIM_FGSWIFTBUS_DBUSOBJECT_H
#include "dbusconnection.h"
#include <deque>
#include <mutex>
namespace FGSwiftBus {
//! DBus base object
class CDBusObject
{
public:
//! Constructor
CDBusObject();
//! Destructor
virtual ~CDBusObject();
//! Set the assigned DBus connection.
//! \remark Currently one object can only manage one connection at a time
void setDBusConnection(const std::shared_ptr<CDBusConnection>& dbusConnection);
//! Register itself with interfaceName and objectPath
//! \warning Before calling this method, make sure that a valid DBus connection was set.
void registerDBusObjectPath(const std::string& interfaceName, const std::string& objectPath);
protected:
//! Handler which is called when DBusCconnection is established
virtual void dbusConnectedHandler() {}
//! DBus message handler
virtual DBusHandlerResult dbusMessageHandler(const CDBusMessage& message) = 0;
//! Handler which is called when DBusConnection disconnected
virtual void dbusDisconnectedHandler() {}
//! Send DBus signal
void sendDBusSignal(const std::string& name);
//! Send DBus message
void sendDBusMessage(const CDBusMessage& message);
//! Maybe sends an empty DBus reply (acknowledgement)
void maybeSendEmptyDBusReply(bool wantsReply, const std::string& destination, dbus_uint32_t serial);
//! Send DBus reply
template <typename T>
void sendDBusReply(const std::string& destination, dbus_uint32_t serial, const T& argument)
{
CDBusMessage reply = CDBusMessage::createReply(destination, serial);
reply.beginArgumentWrite();
reply.appendArgument(argument);
m_dbusConnection->sendMessage(reply);
}
//! Send DBus reply
template <typename T>
void sendDBusReply(const std::string& destination, dbus_uint32_t serial, const std::vector<T>& array)
{
CDBusMessage reply = CDBusMessage::createReply(destination, serial);
reply.beginArgumentWrite();
reply.appendArgument(array);
m_dbusConnection->sendMessage(reply);
}
//! Queue a DBus call to be executed in a different thread
void queueDBusCall(const std::function<void()>& func);
//! Invoke all pending DBus calls. They will be executed in the calling thread.
void invokeQueuedDBusCalls();
private:
static void dbusObjectPathUnregisterFunction(DBusConnection* connection, void* data);
static DBusHandlerResult dbusObjectPathMessageFunction(DBusConnection* connection, DBusMessage* message, void* data);
std::shared_ptr<CDBusConnection> m_dbusConnection;
std::string m_interfaceName;
std::string m_objectPath;
std::mutex m_mutex;
std::deque<std::function<void()>> m_qeuedDBusCalls;
const DBusObjectPathVTable m_dbusObjectPathVTable = {dbusObjectPathUnregisterFunction, dbusObjectPathMessageFunction, nullptr, nullptr, nullptr, nullptr};
};
} // namespace FGSwiftBus
#endif // guard

View File

@@ -0,0 +1,84 @@
/*
* SPDX-FileCopyrightText: (C) 2019-2022 swift Project Community / Contributors (https://swift-project.org/)
* SPDX-FileCopyrightText: (C) 2019-2022 Lars Toenning <dev@ltoenning.de>
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "dbusobject.h"
#include "dbusserver.h"
#include <algorithm>
#include <cassert>
#include <memory>
namespace FGSwiftBus {
CDBusServer::CDBusServer()
{
dbus_threads_init_default();
}
CDBusServer::~CDBusServer()
{
close();
}
bool CDBusServer::listen(const std::string& address)
{
DBusError error;
dbus_error_init(&error);
m_server.reset(dbus_server_listen(address.c_str(), &error));
if (!m_server) {
return false;
}
dbus_server_set_new_connection_function(m_server.get(), onNewConnection, this, nullptr);
return true;
}
bool CDBusServer::isConnected() const
{
return m_server ? dbus_server_get_is_connected(m_server.get()) : false;
}
void CDBusServer::close()
{
if (m_server) { dbus_server_disconnect(m_server.get()); }
}
void CDBusServer::setDispatcher(CDBusDispatcher* dispatcher)
{
assert(dispatcher);
assert(m_server);
m_dispatcher = dispatcher;
dbus_server_set_watch_functions(
m_server.get(),
dispatcher->m_watchCallbacks.add,
dispatcher->m_watchCallbacks.remove,
dispatcher->m_watchCallbacks.toggled,
&dispatcher->m_watchCallbacks, nullptr);
dbus_server_set_timeout_functions(
m_server.get(),
dispatcher->m_timeoutCallbacks.add,
dispatcher->m_timeoutCallbacks.remove,
dispatcher->m_timeoutCallbacks.toggled,
&dispatcher->m_timeoutCallbacks, nullptr);
}
void CDBusServer::onNewConnection(DBusServer*, DBusConnection* conn)
{
auto dbusConnection = std::make_shared<CDBusConnection>(conn);
m_newConnectionFunc(dbusConnection);
}
void CDBusServer::onNewConnection(DBusServer* server, DBusConnection* conn, void* data)
{
auto* obj = static_cast<CDBusServer*>(data);
obj->onNewConnection(server, conn);
}
} // namespace FGSwiftBus

View File

@@ -0,0 +1,79 @@
/*
* SPDX-FileCopyrightText: (C) 2019-2022 swift Project Community / Contributors (https://swift-project.org/)
* SPDX-FileCopyrightText: (C) 2019-2022 Lars Toenning <dev@ltoenning.de>
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef BLACKSIM_FGSWIFTBUS_DBUSSERVER_H
#define BLACKSIM_FGSWIFTBUS_DBUSSERVER_H
#include "dbuscallbacks.h"
#include "dbusdispatcher.h"
#include "dbuserror.h"
#include "dbusmessage.h"
#include <dbus/dbus.h>
#include <event2/event.h>
#include <functional>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
namespace FGSwiftBus {
class CDBusObject;
//! DBus connection
class CDBusServer : public IDispatchable
{
public:
//! New connection handler function
using NewConnectionFunc = std::function<void(std::shared_ptr<CDBusConnection>)>;
//! Constructor
CDBusServer();
//! Destructor
~CDBusServer();
//! Set the dispatcher
void setDispatcher(CDBusDispatcher* dispatcher);
//! Connect to bus
bool listen(const std::string& address);
//! Is connected?
bool isConnected() const;
void dispatch() override {}
//! Close connection
void close();
//! Get the last error
CDBusError lastError() const { return m_lastError; }
//! Set the function to be used for handling new connections.
void setNewConnectionFunc(const NewConnectionFunc& func)
{
m_newConnectionFunc = func;
}
private:
void onNewConnection(DBusServer* server, DBusConnection* conn);
static void onNewConnection(DBusServer* server, DBusConnection* conn, void* data);
struct DBusServerDeleter {
void operator()(DBusServer* obj) const { dbus_server_unref(obj); }
};
CDBusDispatcher* m_dispatcher = nullptr;
std::unique_ptr<DBusServer, DBusServerDeleter> m_server;
CDBusError m_lastError;
NewConnectionFunc m_newConnectionFunc;
};
} // namespace FGSwiftBus
#endif // guard

View File

@@ -0,0 +1,74 @@
/*
* SPDX-FileCopyrightText: (C) 2019-2022 swift Project Community / Contributors (https://swift-project.org/)
* SPDX-FileCopyrightText: (C) 2019-2022 Lars Toenning <dev@ltoenning.de>
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "plugin.h"
#include "service.h"
#include "traffic.h"
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
#include <functional>
#include <iostream>
#include <simgear/structure/commands.hxx>
#include <thread>
namespace {
inline std::string fgswiftbusServiceName()
{
return "org.swift-project.fgswiftbus";
}
} // namespace
namespace FGSwiftBus {
CPlugin::CPlugin()
{
startServer();
}
CPlugin::~CPlugin()
{
if (m_dbusConnection) {
m_dbusConnection->close();
}
m_shouldStop = true;
if (m_dbusThread.joinable()) { m_dbusThread.join(); }
}
void CPlugin::startServer()
{
m_service.reset(new CService());
m_traffic.reset(new CTraffic());
m_dbusP2PServer.reset(new CDBusServer());
std::string ip = fgGetString("/sim/swift/address", "127.0.0.1");
std::string port = fgGetString("/sim/swift/port", "45003");
std::string listenAddress = "tcp:host=" + ip + ",port=" + port;
if (!m_dbusP2PServer->listen(listenAddress)) {
m_service->addTextMessage("FGSwiftBus startup failed!");
return;
}
m_dbusP2PServer->setDispatcher(&m_dbusDispatcher);
m_dbusP2PServer->setNewConnectionFunc([this](const std::shared_ptr<CDBusConnection>& conn) {
m_dbusConnection = conn;
m_dbusConnection->setDispatcher(&m_dbusDispatcher);
m_service->setDBusConnection(m_dbusConnection);
m_service->registerDBusObjectPath(m_service->InterfaceName(), m_service->ObjectPath());
m_traffic->setDBusConnection(m_dbusConnection);
m_traffic->registerDBusObjectPath(m_traffic->InterfaceName(), m_traffic->ObjectPath());
});
SG_LOG(SG_NETWORK, SG_INFO, "FGSwiftBus started");
}
void CPlugin::fastLoop()
{
this->m_dbusDispatcher.runOnce();
this->m_service->process();
this->m_traffic->process();
this->m_traffic->emitSimFrame();
}
} // namespace FGSwiftBus

View File

@@ -0,0 +1,58 @@
/*
* SPDX-FileCopyrightText: (C) 2019-2022 swift Project Community / Contributors (https://swift-project.org/)
* SPDX-FileCopyrightText: (C) 2019-2022 Lars Toenning <dev@ltoenning.de>
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef BLACKSIM_FGSWIFTBUS_PLUGIN_H
#define BLACKSIM_FGSWIFTBUS_PLUGIN_H
//! \file
/*!
* \namespace FGSwiftBus
* Plugin loaded by Flightgear which publishes a DBus service
*/
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include "config.h"
#include "dbusconnection.h"
#include "dbusdispatcher.h"
#include "dbusserver.h"
#include <memory>
#include <thread>
namespace FGSwiftBus {
class CService;
class CTraffic;
/*!
* Main plugin class
*/
class CPlugin
{
public:
//! Constructor
CPlugin();
void startServer();
//! Destructor
~CPlugin();
void fastLoop();
private:
CDBusDispatcher m_dbusDispatcher;
std::unique_ptr<CDBusServer> m_dbusP2PServer;
std::shared_ptr<CDBusConnection> m_dbusConnection;
std::unique_ptr<CService> m_service;
std::unique_ptr<CTraffic> m_traffic;
std::thread m_dbusThread;
bool m_isRunning = false;
bool m_shouldStop = false;
};
} // namespace FGSwiftBus
#endif // BLACKSIM_FGSWIFTBUS_PLUGIN_H

View File

@@ -0,0 +1,633 @@
/*
* Service module for swift<->FG connection
* SPDX-FileCopyrightText: (C) 2019-2022 swift Project Community / Contributors (https://swift-project.org/)
* SPDX-FileCopyrightText: (C) 2019-2022 Lars Toenning <dev@ltoenning.de>
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "service.h"
#include <Main/fg_props.hxx>
#include <iostream>
#include <simgear/constants.h>
#include <simgear/debug/logstream.hxx>
#include <simgear/structure/commands.hxx>
#define FGSWIFTBUS_API_VERSION 3;
namespace FGSwiftBus {
CService::CService()
{
// Initialize node pointers
m_textMessageNode = fgGetNode("/sim/messages/copilot", true);
m_aircraftModelPathNode = fgGetNode("/sim/aircraft-dir", true);
m_aircraftDescriptionNode = fgGetNode("/sim/description", true);
m_isPausedNode = fgGetNode("/sim/freeze/master", true);
m_latitudeNode = fgGetNode("/position/latitude-deg", true);
m_longitudeNode = fgGetNode("/position/longitude-deg", true);
m_altitudeMSLNode = fgGetNode("/position/altitude-ft", true);
m_heightAGLNode = fgGetNode("/position/altitude-agl-ft", true);
m_groundSpeedNode = fgGetNode("/velocities/groundspeed-kt", true);
m_pitchNode = fgGetNode("/orientation/pitch-deg", true);
m_rollNode = fgGetNode("/orientation/roll-deg", true);
m_trueHeadingNode = fgGetNode("/orientation/heading-deg", true);
m_wheelsOnGroundNode = fgGetNode("/gear/gear/wow", true);
m_com1ActiveNode = fgGetNode("/instrumentation/comm/frequencies/selected-mhz", true);
m_com1StandbyNode = fgGetNode("/instrumentation/comm/frequencies/standby-mhz", true);
m_com2ActiveNode = fgGetNode("/instrumentation/comm[1]/frequencies/selected-mhz", true);
m_com2StandbyNode = fgGetNode("/instrumentation/comm[1]/frequencies/standby-mhz", true);
m_transponderCodeNode = fgGetNode("/instrumentation/transponder/id-code", true);
m_transponderModeNode = fgGetNode("/instrumentation/transponder/inputs/knob-mode", true);
m_transponderIdentNode = fgGetNode("/instrumentation/transponder/ident", true);
m_beaconLightsNode = fgGetNode("/controls/lighting/beacon", true);
m_landingLightsNode = fgGetNode("/controls/lighting/landing-lights", true);
m_navLightsNode = fgGetNode("/controls/lighting/nav-lights", true);
m_strobeLightsNode = fgGetNode("/controls/lighting/strobe", true);
m_taxiLightsNode = fgGetNode("/controls/lighting/taxi-light", true);
m_altimeterServiceableNode = fgGetNode("/instrumentation/altimeter/serviceable", true);
m_pressAltitudeFtNode = fgGetNode("/instrumentation/altimeter/pressure-alt-ft", true);
m_flapsDeployRatioNode = fgGetNode("/surface-positions/flap-pos-norm", true);
m_gearDeployRatioNode = fgGetNode("/gear/gear/position-norm", true);
m_speedBrakeDeployRatioNode = fgGetNode("/surface-positions/speedbrake-pos-norm", true);
m_aircraftNameNode = fgGetNode("/sim/aircraft", true);
m_groundElevationNode = fgGetNode("/position/ground-elev-m", true);
m_velocityXNode = fgGetNode("/velocities/speed-east-fps", true);
m_velocityYNode = fgGetNode("/velocities/speed-down-fps", true);
m_velocityZNode = fgGetNode("/velocities/speed-north-fps", true);
m_rollRateNode = fgGetNode("/orientation/roll-rate-degps", true);
m_pichRateNode = fgGetNode("/orientation/pitch-rate-degps", true);
m_yawRateNode = fgGetNode("/orientation/yaw-rate-degps", true);
m_com1VolumeNode = fgGetNode("/instrumentation/comm/volume", true);
m_com2VolumeNode = fgGetNode("/instrumentation/comm[1]/volume", true);
SG_LOG(SG_NETWORK, SG_INFO, "FGSwiftBus Service initialized");
}
const std::string& CService::InterfaceName()
{
static const std::string s(FGSWIFTBUS_SERVICE_INTERFACENAME);
return s;
}
const std::string& CService::ObjectPath()
{
static const std::string s(FGSWIFTBUS_SERVICE_OBJECTPATH);
return s;
}
// Static method
int CService::getVersionNumber()
{
return FGSWIFTBUS_API_VERSION;
}
void CService::addTextMessage(const std::string& text)
{
if (text.empty()) { return; }
m_textMessageNode->setStringValue(text);
}
std::string CService::getAircraftModelPath() const
{
return m_aircraftModelPathNode->getStringValue();
}
std::string CService::getAircraftLivery() const
{
return "";
}
std::string CService::getAircraftIcaoCode() const
{
return "";
}
std::string CService::getAircraftDescription() const
{
return m_aircraftDescriptionNode->getStringValue();
}
bool CService::isPaused() const
{
return m_isPausedNode->getBoolValue();
}
double CService::getLatitude() const
{
return m_latitudeNode->getDoubleValue();
}
double CService::getLongitude() const
{
return m_longitudeNode->getDoubleValue();
}
double CService::getAltitudeMSL() const
{
return m_altitudeMSLNode->getDoubleValue();
}
double CService::getHeightAGL() const
{
return m_heightAGLNode->getDoubleValue();
}
double CService::getGroundSpeed() const
{
return m_groundSpeedNode->getDoubleValue();
}
double CService::getPitch() const
{
return m_pitchNode->getDoubleValue();
}
double CService::getRoll() const
{
return m_rollNode->getDoubleValue();
}
double CService::getTrueHeading() const
{
return m_trueHeadingNode->getDoubleValue();
}
bool CService::getAllWheelsOnGround() const
{
return m_wheelsOnGroundNode->getBoolValue();
}
int CService::getCom1Active() const
{
return (int)(m_com1ActiveNode->getDoubleValue() * 1000);
}
int CService::getCom1Standby() const
{
return (int)(m_com1StandbyNode->getDoubleValue() * 1000);
}
int CService::getCom2Active() const
{
return (int)(m_com2ActiveNode->getDoubleValue() * 1000);
}
int CService::getCom2Standby() const
{
return (int)(m_com2StandbyNode->getDoubleValue() * 1000);
}
int CService::getTransponderCode() const
{
return m_transponderCodeNode->getIntValue();
}
int CService::getTransponderMode() const
{
return m_transponderModeNode->getIntValue();
}
bool CService::getTransponderIdent() const
{
return m_transponderIdentNode->getBoolValue();
}
bool CService::getBeaconLightsOn() const
{
return m_beaconLightsNode->getBoolValue();
}
bool CService::getLandingLightsOn() const
{
return m_landingLightsNode->getBoolValue();
}
bool CService::getNavLightsOn() const
{
return m_navLightsNode->getBoolValue();
}
bool CService::getStrobeLightsOn() const
{
return m_strobeLightsNode->getBoolValue();
}
bool CService::getTaxiLightsOn() const
{
return m_taxiLightsNode->getBoolValue();
}
double CService::getPressAlt() const
{
if (m_altimeterServiceableNode->getBoolValue()) {
return m_pressAltitudeFtNode->getDoubleValue();
} else {
return m_altitudeMSLNode->getDoubleValue();
}
}
void CService::setCom1Active(int freq)
{
m_com1ActiveNode->setDoubleValue(freq / (double)1000);
}
void CService::setCom1Standby(int freq)
{
m_com1StandbyNode->setDoubleValue(freq / (double)1000);
}
void CService::setCom2Active(int freq)
{
m_com2ActiveNode->setDoubleValue(freq / (double)1000);
}
void CService::setCom2Standby(int freq)
{
m_com2StandbyNode->setDoubleValue(freq / (double)1000);
}
void CService::setTransponderCode(int code)
{
m_transponderCodeNode->setIntValue(code);
}
void CService::setTransponderMode(int mode)
{
m_transponderModeNode->setIntValue(mode);
}
double CService::getFlapsDeployRatio() const
{
return m_flapsDeployRatioNode->getFloatValue();
}
double CService::getGearDeployRatio() const
{
return m_gearDeployRatioNode->getFloatValue();
}
int CService::getNumberOfEngines() const
{
// TODO Use correct property
return 2;
}
std::vector<double> CService::getEngineN1Percentage() const
{
// TODO use correct engine numbers
std::vector<double> list;
const auto number = static_cast<unsigned int>(getNumberOfEngines());
list.reserve(number);
for (unsigned int engineNumber = 0; engineNumber < number; ++engineNumber) {
list.push_back(fgGetDouble("/engine/engine/n1"));
}
return list;
}
double CService::getSpeedBrakeRatio() const
{
return m_speedBrakeDeployRatioNode->getFloatValue();
}
double CService::getGroundElevation() const
{
return m_groundElevationNode->getDoubleValue();
}
std::string CService::getAircraftModelFilename() const
{
std::string modelFileName = getAircraftName();
modelFileName.append("-set.xml");
return modelFileName;
}
std::string CService::getAircraftModelString() const
{
std::string modelName = getAircraftName();
std::string modelString = "FG " + modelName;
return modelString;
}
std::string CService::getAircraftName() const
{
return m_aircraftNameNode->getStringValue();
}
double CService::getVelocityX() const
{
return m_velocityXNode->getDoubleValue() * SG_FEET_TO_METER;
}
double CService::getVelocityY() const
{
return m_velocityYNode->getDoubleValue() * SG_FEET_TO_METER * -1; // + (up), - (down)
}
double CService::getVelocityZ() const
{
return m_velocityZNode->getDoubleValue() * SG_FEET_TO_METER;
}
double CService::getRollRate() const
{
return m_rollRateNode->getDoubleValue() * SG_DEGREES_TO_RADIANS;
}
double CService::getPitchRate() const
{
return m_pichRateNode->getDoubleValue() * SG_DEGREES_TO_RADIANS;
}
double CService::getYawRate() const
{
return m_yawRateNode->getDoubleValue() * SG_DEGREES_TO_RADIANS;
}
double CService::getCom1Volume() const
{
return m_com1VolumeNode->getDoubleValue();
}
double CService::getCom2Volume() const
{
return m_com2VolumeNode->getDoubleValue();
}
static const char* introspection_service = DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE;
DBusHandlerResult CService::dbusMessageHandler(const CDBusMessage& message_)
{
CDBusMessage message(message_);
const std::string sender = message.getSender();
const dbus_uint32_t serial = message.getSerial();
const bool wantsReply = message.wantsReply();
if (message.getInterfaceName() == DBUS_INTERFACE_INTROSPECTABLE) {
if (message.getMethodName() == "Introspect") {
sendDBusReply(sender, serial, introspection_service);
}
} else if (message.getInterfaceName() == FGSWIFTBUS_SERVICE_INTERFACENAME) {
if (message.getMethodName() == "addTextMessage") {
maybeSendEmptyDBusReply(wantsReply, sender, serial);
std::string text;
message.beginArgumentRead();
message.getArgument(text);
queueDBusCall([=]() {
addTextMessage(text);
});
} else if (message.getMethodName() == "getOwnAircraftSituationData") {
queueDBusCall([=]() {
double lat = getLatitude();
double lon = getLongitude();
double alt = getAltitudeMSL();
double gs = getGroundSpeed();
double pitch = getPitch();
double roll = getRoll();
double trueHeading = getTrueHeading();
double pressAlt = getPressAlt();
CDBusMessage reply = CDBusMessage::createReply(sender, serial);
reply.beginArgumentWrite();
reply.appendArgument(lat);
reply.appendArgument(lon);
reply.appendArgument(alt);
reply.appendArgument(gs);
reply.appendArgument(pitch);
reply.appendArgument(roll);
reply.appendArgument(trueHeading);
reply.appendArgument(pressAlt);
sendDBusMessage(reply);
});
} else if (message.getMethodName() == "getOwnAircraftVelocityData") {
queueDBusCall([=]() {
double velocityX = getVelocityX();
double velocityY = getVelocityY();
double velocityZ = getVelocityZ();
double pitchVelocity = getPitchRate();
double rollVelocity = getRollRate();
double yawVelocity = getYawRate();
CDBusMessage reply = CDBusMessage::createReply(sender, serial);
reply.beginArgumentWrite();
reply.appendArgument(velocityX);
reply.appendArgument(velocityY);
reply.appendArgument(velocityZ);
reply.appendArgument(pitchVelocity);
reply.appendArgument(rollVelocity);
reply.appendArgument(yawVelocity);
sendDBusMessage(reply);
});
} else if (message.getMethodName() == "getVersionNumber") {
queueDBusCall([=]() {
sendDBusReply(sender, serial, getVersionNumber());
});
} else if (message.getMethodName() == "getAircraftModelPath") {
queueDBusCall([=]() {
sendDBusReply(sender, serial, getAircraftModelPath());
});
} else if (message.getMethodName() == "getAircraftModelFilename") {
queueDBusCall([=]() {
sendDBusReply(sender, serial, getAircraftModelFilename());
});
} else if (message.getMethodName() == "getAircraftModelString") {
queueDBusCall([=]() {
sendDBusReply(sender, serial, getAircraftModelString());
});
} else if (message.getMethodName() == "getAircraftName") {
queueDBusCall([=]() {
sendDBusReply(sender, serial, getAircraftName());
});
} else if (message.getMethodName() == "getAircraftLivery") {
queueDBusCall([=]() {
sendDBusReply(sender, serial, getAircraftLivery());
});
} else if (message.getMethodName() == "getAircraftIcaoCode") {
queueDBusCall([=]() {
sendDBusReply(sender, serial, getAircraftIcaoCode());
});
} else if (message.getMethodName() == "getAircraftDescription") {
queueDBusCall([=]() {
sendDBusReply(sender, serial, getAircraftDescription());
});
} else if (message.getMethodName() == "isPaused") {
queueDBusCall([=]() {
sendDBusReply(sender, serial, isPaused());
});
} else if (message.getMethodName() == "getLatitudeDeg") {
queueDBusCall([=]() {
sendDBusReply(sender, serial, getLatitude());
});
} else if (message.getMethodName() == "getLongitudeDeg") {
queueDBusCall([=]() {
sendDBusReply(sender, serial, getLongitude());
});
} else if (message.getMethodName() == "getAltitudeMslFt") {
queueDBusCall([=]() {
sendDBusReply(sender, serial, getAltitudeMSL());
});
} else if (message.getMethodName() == "getHeightAglFt") {
queueDBusCall([=]() {
sendDBusReply(sender, serial, getHeightAGL());
});
} else if (message.getMethodName() == "getGroundSpeedKts") {
queueDBusCall([=]() {
sendDBusReply(sender, serial, getGroundSpeed());
});
} else if (message.getMethodName() == "getPitchDeg") {
queueDBusCall([=]() {
sendDBusReply(sender, serial, getPitch());
});
} else if (message.getMethodName() == "getRollDeg") {
queueDBusCall([=]() {
sendDBusReply(sender, serial, getRoll());
});
} else if (message.getMethodName() == "getAllWheelsOnGround") {
queueDBusCall([=]() {
sendDBusReply(sender, serial, getAllWheelsOnGround());
});
} else if (message.getMethodName() == "getCom1ActiveKhz") {
queueDBusCall([=]() {
sendDBusReply(sender, serial, getCom1Active());
});
} else if (message.getMethodName() == "getCom1StandbyKhz") {
queueDBusCall([=]() {
sendDBusReply(sender, serial, getCom1Standby());
});
} else if (message.getMethodName() == "getCom2ActiveKhz") {
queueDBusCall([=]() {
sendDBusReply(sender, serial, getCom2Active());
});
} else if (message.getMethodName() == "getCom2StandbyKhz") {
queueDBusCall([=]() {
sendDBusReply(sender, serial, getCom2Standby());
});
} else if (message.getMethodName() == "getTransponderCode") {
queueDBusCall([=]() {
sendDBusReply(sender, serial, getTransponderCode());
});
} else if (message.getMethodName() == "getTransponderMode") {
queueDBusCall([=]() {
sendDBusReply(sender, serial, getTransponderMode());
});
} else if (message.getMethodName() == "getTransponderIdent") {
queueDBusCall([=]() {
sendDBusReply(sender, serial, getTransponderIdent());
});
} else if (message.getMethodName() == "getBeaconLightsOn") {
queueDBusCall([=]() {
sendDBusReply(sender, serial, getBeaconLightsOn());
});
} else if (message.getMethodName() == "getLandingLightsOn") {
queueDBusCall([=]() {
sendDBusReply(sender, serial, getLandingLightsOn());
});
} else if (message.getMethodName() == "getNavLightsOn") {
queueDBusCall([=]() {
sendDBusReply(sender, serial, getNavLightsOn());
});
} else if (message.getMethodName() == "getStrobeLightsOn") {
queueDBusCall([=]() {
sendDBusReply(sender, serial, getStrobeLightsOn());
});
} else if (message.getMethodName() == "getTaxiLightsOn") {
queueDBusCall([=]() {
sendDBusReply(sender, serial, getTaxiLightsOn());
});
} else if (message.getMethodName() == "getPressAlt") {
queueDBusCall([=]() {
sendDBusReply(sender, serial, getPressAlt());
});
} else if (message.getMethodName() == "getGroundElevation") {
queueDBusCall([=]() {
sendDBusReply(sender, serial, getGroundElevation());
});
} else if (message.getMethodName() == "setCom1ActiveKhz") {
maybeSendEmptyDBusReply(wantsReply, sender, serial);
int frequency = 0;
message.beginArgumentRead();
message.getArgument(frequency);
queueDBusCall([=]() {
setCom1Active(frequency);
});
} else if (message.getMethodName() == "setCom1StandbyKhz") {
maybeSendEmptyDBusReply(wantsReply, sender, serial);
int frequency = 0;
message.beginArgumentRead();
message.getArgument(frequency);
queueDBusCall([=]() {
setCom1Standby(frequency);
});
} else if (message.getMethodName() == "setCom2ActiveKhz") {
maybeSendEmptyDBusReply(wantsReply, sender, serial);
int frequency = 0;
message.beginArgumentRead();
message.getArgument(frequency);
queueDBusCall([=]() {
setCom2Active(frequency);
});
} else if (message.getMethodName() == "setCom2StandbyKhz") {
maybeSendEmptyDBusReply(wantsReply, sender, serial);
int frequency = 0;
message.beginArgumentRead();
message.getArgument(frequency);
queueDBusCall([=]() {
setCom2Standby(frequency);
});
} else if (message.getMethodName() == "setTransponderCode") {
maybeSendEmptyDBusReply(wantsReply, sender, serial);
int code = 0;
message.beginArgumentRead();
message.getArgument(code);
queueDBusCall([=]() {
setTransponderCode(code);
});
} else if (message.getMethodName() == "setTransponderMode") {
maybeSendEmptyDBusReply(wantsReply, sender, serial);
int mode = 0;
message.beginArgumentRead();
message.getArgument(mode);
queueDBusCall([=]() {
setTransponderMode(mode);
});
} else if (message.getMethodName() == "getFlapsDeployRatio") {
queueDBusCall([=]() {
sendDBusReply(sender, serial, getFlapsDeployRatio());
});
} else if (message.getMethodName() == "getGearDeployRatio") {
queueDBusCall([=]() {
sendDBusReply(sender, serial, getGearDeployRatio());
});
} else if (message.getMethodName() == "getEngineN1Percentage") {
queueDBusCall([=]() {
std::vector<double> array = getEngineN1Percentage();
sendDBusReply(sender, serial, array);
});
} else if (message.getMethodName() == "getSpeedBrakeRatio") {
queueDBusCall([=]() {
sendDBusReply(sender, serial, getSpeedBrakeRatio());
});
} else if (message.getMethodName() == "getCom1Volume") {
queueDBusCall([=]() {
sendDBusReply(sender, serial, getCom1Volume());
});
} else if (message.getMethodName() == "getCom2Volume") {
queueDBusCall([=]() {
sendDBusReply(sender, serial, getCom2Volume());
});
} else {
// Unknown message. Tell DBus that we cannot handle it
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
}
return DBUS_HANDLER_RESULT_HANDLED;
}
int CService::process()
{
invokeQueuedDBusCalls();
return 1;
}
} // namespace FGSwiftBus

262
src/Network/Swift/service.h Normal file
View File

@@ -0,0 +1,262 @@
/*
* Service module for swift<->FG connection
* SPDX-FileCopyrightText: (C) 2019-2022 swift Project Community / Contributors (https://swift-project.org/)
* SPDX-FileCopyrightText: (C) 2019-2022 Lars Toenning <dev@ltoenning.de>
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef BLACKSIM_FGSWIFTBUS_SERVICE_H
#define BLACKSIM_FGSWIFTBUS_SERVICE_H
//! \file
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include "dbusobject.h"
#include <Main/fg_props.hxx>
#include <chrono>
#include <simgear/compiler.h>
#include <simgear/debug/logstream.hxx>
#include <simgear/io/raw_socket.hxx>
#include <simgear/misc/stdint.hxx>
#include <simgear/props/props.hxx>
#include <simgear/structure/commands.hxx>
#include <simgear/structure/event_mgr.hxx>
#include <simgear/structure/subsystem_mgr.hxx>
#include <simgear/timing/timestamp.hxx>
#include <string>
//! \cond PRIVATE
#define FGSWIFTBUS_SERVICE_INTERFACENAME "org.swift_project.fgswiftbus.service"
#define FGSWIFTBUS_SERVICE_OBJECTPATH "/fgswiftbus/service"
//! \endcond
namespace FGSwiftBus {
/*!
* FGSwiftBus service object which is accessible through DBus
*/
class CService : public CDBusObject
{
public:
//! Constructor
CService();
//! DBus interface name
static const std::string& InterfaceName();
//! DBus object path
static const std::string& ObjectPath();
//! Getting flightgear version
static int getVersionNumber();
////! Add a text message to the on-screen display, with RGB components in the range [0,1]
void addTextMessage(const std::string& text);
////! Get full path to current aircraft model
std::string getAircraftModelPath() const;
////! Get base filename of current aircraft model
std::string getAircraftModelFilename() const;
////! Get canonical swift model string of current aircraft model
std::string getAircraftModelString() const;
////! Get name of current aircraft model
std::string getAircraftName() const;
////! Get path to current aircraft livery
std::string getAircraftLivery() const;
//! Get the ICAO code of the current aircraft model
std::string getAircraftIcaoCode() const;
////! Get the description of the current aircraft model
std::string getAircraftDescription() const;
//! True if sim is paused
bool isPaused() const;
//! Get aircraft latitude in degrees
double getLatitude() const;
//! Get aircraft longitude in degrees
double getLongitude() const;
//! Get aircraft altitude in feet
double getAltitudeMSL() const;
//! Get aircraft height in feet
double getHeightAGL() const;
//! Get aircraft groundspeed in knots
double getGroundSpeed() const;
//! Get aircraft pitch in degrees above horizon
double getPitch() const;
//! Get aircraft roll in degrees
double getRoll() const;
//! Get aircraft true heading in degrees
double getTrueHeading() const;
//! Get whether all wheels are on the ground
bool getAllWheelsOnGround() const;
//! Get the current COM1 active frequency in kHz
int getCom1Active() const;
//! Get the current COM1 standby frequency in kHz
int getCom1Standby() const;
//! Get the current COM2 active frequency in kHz
int getCom2Active() const;
//! Get the current COM2 standby frequency in kHz
int getCom2Standby() const;
//! Get the current transponder code in decimal
int getTransponderCode() const;
//! Get the current transponder mode (depends on the aircraft, 0-2 usually mean standby, >2 active)
int getTransponderMode() const;
//! Get whether we are currently squawking ident
bool getTransponderIdent() const;
//! Get whether beacon lights are on
bool getBeaconLightsOn() const;
//! Get whether landing lights are on
bool getLandingLightsOn() const;
//! Get whether nav lights are on
bool getNavLightsOn() const;
//! Get whether strobe lights are on
bool getStrobeLightsOn() const;
//! Get whether taxi lights are on
bool getTaxiLightsOn() const;
//! Get pressure altitude in ft
double getPressAlt() const;
//! Set the current COM1 active frequency in kHz
void setCom1Active(int freq);
//! Set the current COM1 standby frequency in kHz
void setCom1Standby(int freq);
//! Set the current COM2 active frequency in kHz
void setCom2Active(int freq);
//! Set the current COM2 standby frequency in kHz
void setCom2Standby(int freq);
////! Set the current transponder code in decimal
void setTransponderCode(int code);
////! Set the current transponder mode (depends on the aircraft, 0 and 1 usually mean standby, >1 active)
void setTransponderMode(int mode);
//! Get flaps deploy ratio, where 0.0 is flaps fully retracted, and 1.0 is flaps fully extended.
double getFlapsDeployRatio() const;
//! Get gear deploy ratio, where 0 is up and 1 is down
double getGearDeployRatio() const;
//! Get the number of engines of current aircraft
int getNumberOfEngines() const;
//! Get the N1 speed as percent of max (per engine)
std::vector<double> getEngineN1Percentage() const;
//! Get the ratio how much the speedbrakes surfaces are extended (0.0 is fully retracted, and 1.0 is fully extended)
double getSpeedBrakeRatio() const;
//! Get ground elevation at aircraft current position
double getGroundElevation() const;
//! Get x velocity in m/s
double getVelocityX() const;
//! Get y velocity in m/s
double getVelocityY() const;
//! Get z velocity in m/s
double getVelocityZ() const;
//! Get roll rate in rad/sec
double getRollRate() const;
//! Get pitch rate in rad/sec
double getPitchRate() const;
//! Get yaw rate in rad/sec
double getYawRate() const;
double getCom1Volume() const;
double getCom2Volume() const;
//! Perform generic processing
int process();
protected:
DBusHandlerResult dbusMessageHandler(const CDBusMessage& message) override;
private:
SGPropertyNode_ptr m_textMessageNode;
SGPropertyNode_ptr m_aircraftModelPathNode;
//SGPropertyNode_ptr aircraftLiveryNode;
//SGPropertyNode_ptr aircraftIcaoCodeNode;
SGPropertyNode_ptr m_aircraftDescriptionNode;
SGPropertyNode_ptr m_isPausedNode;
SGPropertyNode_ptr m_latitudeNode;
SGPropertyNode_ptr m_longitudeNode;
SGPropertyNode_ptr m_altitudeMSLNode;
SGPropertyNode_ptr m_heightAGLNode;
SGPropertyNode_ptr m_groundSpeedNode;
SGPropertyNode_ptr m_pitchNode;
SGPropertyNode_ptr m_rollNode;
SGPropertyNode_ptr m_trueHeadingNode;
SGPropertyNode_ptr m_wheelsOnGroundNode;
SGPropertyNode_ptr m_com1ActiveNode;
SGPropertyNode_ptr m_com1StandbyNode;
SGPropertyNode_ptr m_com2ActiveNode;
SGPropertyNode_ptr m_com2StandbyNode;
SGPropertyNode_ptr m_transponderCodeNode;
SGPropertyNode_ptr m_transponderModeNode;
SGPropertyNode_ptr m_transponderIdentNode;
SGPropertyNode_ptr m_beaconLightsNode;
SGPropertyNode_ptr m_landingLightsNode;
SGPropertyNode_ptr m_navLightsNode;
SGPropertyNode_ptr m_strobeLightsNode;
SGPropertyNode_ptr m_taxiLightsNode;
SGPropertyNode_ptr m_altimeterServiceableNode;
SGPropertyNode_ptr m_pressAltitudeFtNode;
SGPropertyNode_ptr m_flapsDeployRatioNode;
SGPropertyNode_ptr m_gearDeployRatioNode;
SGPropertyNode_ptr m_speedBrakeDeployRatioNode;
SGPropertyNode_ptr m_groundElevationNode;
//SGPropertyNode_ptr numberEnginesNode;
//SGPropertyNode_ptr engineN1PercentageNode;
SGPropertyNode_ptr m_aircraftNameNode;
SGPropertyNode_ptr m_velocityXNode;
SGPropertyNode_ptr m_velocityYNode;
SGPropertyNode_ptr m_velocityZNode;
SGPropertyNode_ptr m_rollRateNode;
SGPropertyNode_ptr m_pichRateNode;
SGPropertyNode_ptr m_yawRateNode;
SGPropertyNode_ptr m_com1VolumeNode;
SGPropertyNode_ptr m_com2VolumeNode;
};
} // namespace FGSwiftBus
#endif // guard

View File

@@ -0,0 +1,97 @@
/*
* SPDX-FileCopyrightText: (C) 2019-2022 swift Project Community / Contributors (https://swift-project.org/)
* SPDX-FileCopyrightText: (C) 2019-2022 Lars Toenning <dev@ltoenning.de>
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include "plugin.h"
#include "swift_connection.hxx"
#include <Main/fg_props.hxx>
#include <simgear/props/props.hxx>
#include <simgear/structure/commands.hxx>
#include <simgear/structure/event_mgr.hxx>
#include <simgear/structure/subsystem_mgr.hxx>
namespace {
inline std::string fgswiftbusServiceName()
{
return std::string("org.swift-project.fgswiftbus");
}
} // namespace
bool SwiftConnection::startServer(const SGPropertyNode* arg, SGPropertyNode* root)
{
SwiftConnection::plug = std::make_unique<FGSwiftBus::CPlugin>();
serverRunning = true;
fgSetBool("/sim/swift/serverRunning", true);
return true;
}
bool SwiftConnection::stopServer(const SGPropertyNode* arg, SGPropertyNode* root)
{
fgSetBool("/sim/swift/serverRunning", false);
serverRunning = false;
SwiftConnection::plug.reset();
return true;
}
SwiftConnection::SwiftConnection()
{
init();
}
SwiftConnection::~SwiftConnection()
{
shutdown();
if (serverRunning) {
SwiftConnection::plug.reset();
}
}
void SwiftConnection::init()
{
if (!initialized) {
globals->get_commands()->addCommand("swiftStart", this, &SwiftConnection::startServer);
globals->get_commands()->addCommand("swiftStop", this, &SwiftConnection::stopServer);
fgSetBool("/sim/swift/available", true);
initialized = true;
}
}
void SwiftConnection::update(double delta_time_sec)
{
if (serverRunning) {
SwiftConnection::plug->fastLoop();
}
}
void SwiftConnection::shutdown()
{
if (initialized) {
fgSetBool("/sim/swift/available", false);
initialized = false;
globals->get_commands()->removeCommand("swiftStart");
globals->get_commands()->removeCommand("swiftStop");
}
}
void SwiftConnection::reinit()
{
shutdown();
init();
}
// Register the subsystem.
SGSubsystemMgr::Registrant<SwiftConnection> registrantSwiftConnection(
SGSubsystemMgr::POST_FDM);

View File

@@ -0,0 +1,49 @@
/*
* SPDX-FileCopyrightText: (C) 2019-2022 swift Project Community / Contributors (https://swift-project.org/)
* SPDX-FileCopyrightText: (C) 2019-2022 Lars Toenning <dev@ltoenning.de>
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <memory>
#include <thread>
#include <Main/fg_props.hxx>
#include <simgear/compiler.h>
#include <simgear/io/raw_socket.hxx>
#include <simgear/props/props.hxx>
#include <simgear/structure/subsystem_mgr.hxx>
#include "dbusconnection.h"
#include "dbusdispatcher.h"
#include "dbusserver.h"
#include "plugin.h"
#ifndef NOMINMAX
#define NOMINMAX
#endif
class SwiftConnection : public SGSubsystem
{
public:
SwiftConnection();
~SwiftConnection();
// Subsystem API.
void init() override;
void reinit() override;
void shutdown() override;
void update(double delta_time_sec) override;
// Subsystem identification.
static const char* staticSubsystemClassId() { return "swift"; }
bool startServer(const SGPropertyNode* arg, SGPropertyNode* root);
bool stopServer(const SGPropertyNode* arg, SGPropertyNode* root);
std::unique_ptr<FGSwiftBus::CPlugin> plug{};
private:
bool serverRunning = false;
bool initialized = false;
};

View File

@@ -0,0 +1,299 @@
/*
* Traffic module for swift<->FG connection
* SPDX-FileCopyrightText: (C) 2019-2022 swift Project Community / Contributors (https://swift-project.org/)
* SPDX-FileCopyrightText: (C) 2019-2022 Lars Toenning <dev@ltoenning.de>
* SPDX-License-Identifier: GPL-2.0-or-later
*/
//! \cond PRIVATE
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include "traffic.h"
#include "SwiftAircraftManager.h"
#include <algorithm>
#include <iostream>
// clazy:excludeall=reserve-candidates
namespace FGSwiftBus {
CTraffic::CTraffic()
{
SG_LOG(SG_NETWORK, SG_INFO, "FGSwiftBus Traffic started");
}
CTraffic::~CTraffic()
{
cleanup();
SG_LOG(SG_NETWORK, SG_INFO, "FGSwiftBus Traffic stopped");
}
const std::string& CTraffic::InterfaceName()
{
static std::string s(FGSWIFTBUS_TRAFFIC_INTERFACENAME);
return s;
}
const std::string& CTraffic::ObjectPath()
{
static std::string s(FGSWIFTBUS_TRAFFIC_OBJECTPATH);
return s;
}
bool CTraffic::initialize()
{
acm.reset(new FGSwiftAircraftManager());
return acm->isInitialized();
}
void CTraffic::emitSimFrame()
{
if (m_emitSimFrame) { sendDBusSignal("simFrame"); }
m_emitSimFrame = !m_emitSimFrame;
}
void CTraffic::emitPlaneAdded(const std::string& callsign)
{
CDBusMessage signalPlaneAdded = CDBusMessage::createSignal(FGSWIFTBUS_TRAFFIC_OBJECTPATH, FGSWIFTBUS_TRAFFIC_INTERFACENAME, "remoteAircraftAdded");
signalPlaneAdded.beginArgumentWrite();
signalPlaneAdded.appendArgument(callsign);
sendDBusMessage(signalPlaneAdded);
}
void CTraffic::cleanup()
{
acm.reset();
}
void CTraffic::dbusDisconnectedHandler()
{
if (acm)
acm->removeAllPlanes();
}
const char* introspection_traffic = DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE;
DBusHandlerResult CTraffic::dbusMessageHandler(const CDBusMessage& message_)
{
CDBusMessage message(message_);
const std::string sender = message.getSender();
const dbus_uint32_t serial = message.getSerial();
const bool wantsReply = message.wantsReply();
if (message.getInterfaceName() == DBUS_INTERFACE_INTROSPECTABLE) {
if (message.getMethodName() == "Introspect") {
sendDBusReply(sender, serial, introspection_traffic);
}
} else if (message.getInterfaceName() == FGSWIFTBUS_TRAFFIC_INTERFACENAME) {
if (message.getMethodName() == "acquireMultiplayerPlanes") {
queueDBusCall([=]() {
std::string owner;
bool acquired = true;
CDBusMessage reply = CDBusMessage::createReply(sender, serial);
reply.beginArgumentWrite();
reply.appendArgument(acquired);
reply.appendArgument(owner);
sendDBusMessage(reply);
});
} else if (message.getMethodName() == "initialize") {
sendDBusReply(sender, serial, initialize());
} else if (message.getMethodName() == "cleanup") {
maybeSendEmptyDBusReply(wantsReply, sender, serial);
queueDBusCall([=]() {
cleanup();
});
} else if (message.getMethodName() == "addPlane") {
maybeSendEmptyDBusReply(wantsReply, sender, serial);
std::string callsign;
std::string modelName;
std::string aircraftIcao;
std::string airlineIcao;
std::string livery;
message.beginArgumentRead();
message.getArgument(callsign);
message.getArgument(modelName);
message.getArgument(aircraftIcao);
message.getArgument(airlineIcao);
message.getArgument(livery);
queueDBusCall([=]() {
if (acm->addPlane(callsign, modelName)) {
emitPlaneAdded(callsign);
}
});
} else if (message.getMethodName() == "removePlane") {
maybeSendEmptyDBusReply(wantsReply, sender, serial);
std::string callsign;
message.beginArgumentRead();
message.getArgument(callsign);
queueDBusCall([=]() {
acm->removePlane(callsign);
});
} else if (message.getMethodName() == "removeAllPlanes") {
maybeSendEmptyDBusReply(wantsReply, sender, serial);
queueDBusCall([=]() {
acm->removeAllPlanes();
});
} else if (message.getMethodName() == "setPlanesPositions") {
maybeSendEmptyDBusReply(wantsReply, sender, serial);
std::vector<std::string> callsigns;
std::vector<double> latitudes;
std::vector<double> longitudes;
std::vector<double> altitudes;
std::vector<double> pitches;
std::vector<double> rolls;
std::vector<double> headings;
std::vector<double> groundspeeds;
std::vector<bool> onGrounds;
message.beginArgumentRead();
message.getArgument(callsigns);
message.getArgument(latitudes);
message.getArgument(longitudes);
message.getArgument(altitudes);
message.getArgument(pitches);
message.getArgument(rolls);
message.getArgument(headings);
message.getArgument(groundspeeds);
message.getArgument(onGrounds);
queueDBusCall([=]() {
std::vector<SwiftPlaneUpdate> updates;
for (long unsigned int i = 0; i < latitudes.size(); i++) {
SGGeod pos;
pos.setLatitudeDeg(latitudes.at(i));
pos.setLongitudeDeg(longitudes.at(i));
pos.setElevationFt(altitudes.at(i));
SGVec3d orientation(pitches.at(i), rolls.at(i), headings.at(i));
updates.push_back({callsigns.at(i), pos, orientation, groundspeeds.at(i), onGrounds.at(i)});
}
acm->updatePlanes(updates);
});
} else if (message.getMethodName() == "getRemoteAircraftData") {
std::vector<std::string> requestedcallsigns;
message.beginArgumentRead();
message.getArgument(requestedcallsigns);
queueDBusCall([=]() {
std::vector<std::string> callsigns = requestedcallsigns;
std::vector<double> latitudesDeg;
std::vector<double> longitudesDeg;
std::vector<double> elevationsM;
std::vector<double> verticalOffsets;
acm->getRemoteAircraftData(callsigns, latitudesDeg, longitudesDeg, elevationsM, verticalOffsets);
CDBusMessage reply = CDBusMessage::createReply(sender, serial);
reply.beginArgumentWrite();
reply.appendArgument(callsigns);
reply.appendArgument(latitudesDeg);
reply.appendArgument(longitudesDeg);
reply.appendArgument(elevationsM);
reply.appendArgument(verticalOffsets);
sendDBusMessage(reply);
});
} else if (message.getMethodName() == "getElevationAtPosition") {
std::string callsign;
double latitudeDeg;
double longitudeDeg;
double altitudeMeters;
message.beginArgumentRead();
message.getArgument(callsign);
message.getArgument(latitudeDeg);
message.getArgument(longitudeDeg);
message.getArgument(altitudeMeters);
queueDBusCall([=]() {
SGGeod pos;
pos.setLatitudeDeg(latitudeDeg);
pos.setLongitudeDeg(longitudeDeg);
pos.setElevationM(altitudeMeters);
double elevation = acm->getElevationAtPosition(callsign, pos);
CDBusMessage reply = CDBusMessage::createReply(sender, serial);
reply.beginArgumentWrite();
reply.appendArgument(callsign);
reply.appendArgument(elevation);
sendDBusMessage(reply);
});
} else if (message.getMethodName() == "setPlanesTransponders") {
maybeSendEmptyDBusReply(wantsReply, sender, serial);
std::vector<std::string> callsigns;
std::vector<int> codes;
std::vector<bool> modeCs;
std::vector<bool> idents;
message.beginArgumentRead();
message.getArgument(callsigns);
message.getArgument(codes);
message.getArgument(modeCs);
message.getArgument(idents);
std::vector<AircraftTransponder> transponders;
transponders.reserve(callsigns.size());
for (long unsigned int i = 0; i < callsigns.size(); i++) {
transponders.emplace_back(callsigns.at(i), codes.at(i), modeCs.at(i), idents.at(i));
}
queueDBusCall([=]() {
acm->setPlanesTransponders(transponders);
});
} else if (message.getMethodName() == "setPlanesSurfaces") {
maybeSendEmptyDBusReply(wantsReply, sender, serial);
std::vector<std::string> callsigns;
std::vector<double> gears;
std::vector<double> flaps;
std::vector<double> spoilers;
std::vector<double> speedBrakes;
std::vector<double> slats;
std::vector<double> wingSweeps;
std::vector<double> thrusts;
std::vector<double> elevators;
std::vector<double> rudders;
std::vector<double> ailerons;
std::vector<bool> landLights;
std::vector<bool> taxiLights;
std::vector<bool> beaconLights;
std::vector<bool> strobeLights;
std::vector<bool> navLights;
std::vector<int> lightPatterns;
message.beginArgumentRead();
message.getArgument(callsigns);
message.getArgument(gears);
message.getArgument(flaps);
message.getArgument(spoilers);
message.getArgument(speedBrakes);
message.getArgument(slats);
message.getArgument(wingSweeps);
message.getArgument(thrusts);
message.getArgument(elevators);
message.getArgument(rudders);
message.getArgument(ailerons);
message.getArgument(landLights);
message.getArgument(taxiLights);
message.getArgument(beaconLights);
message.getArgument(strobeLights);
message.getArgument(navLights);
message.getArgument(lightPatterns);
std::vector<AircraftSurfaces> surfaces;
surfaces.reserve(callsigns.size());
for (long unsigned int i = 0; i < callsigns.size(); i++) {
surfaces.emplace_back(callsigns.at(i), gears.at(i), flaps.at(i), spoilers.at(i), speedBrakes.at(i), slats.at(i),
wingSweeps.at(i), thrusts.at(i), elevators.at(i), rudders.at(i), ailerons.at(i),
landLights.at(i), taxiLights.at(i), beaconLights.at(i), strobeLights.at(i), navLights.at(i), lightPatterns.at(i));
}
queueDBusCall([=]() {
acm->setPlanesSurfaces(surfaces);
});
} else {
// Unknown message. Tell DBus that we cannot handle it
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
}
return DBUS_HANDLER_RESULT_HANDLED;
}
int CTraffic::process()
{
invokeQueuedDBusCalls();
return 1;
}
} // namespace FGSwiftBus
//! \endcond

View File

@@ -0,0 +1,71 @@
/*
* Traffic module for swift<->FG connection
* SPDX-FileCopyrightText: (C) 2019-2022 swift Project Community / Contributors (https://swift-project.org/)
* SPDX-FileCopyrightText: (C) 2019-2022 Lars Toenning <dev@ltoenning.de>
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef BLACKSIM_FGSWIFTBUS_TRAFFIC_H
#define BLACKSIM_FGSWIFTBUS_TRAFFIC_H
//! \file
#include "SwiftAircraftManager.h"
#include "dbusobject.h"
#include <functional>
#include <utility>
//! \cond PRIVATE
#define FGSWIFTBUS_TRAFFIC_INTERFACENAME "org.swift_project.fgswiftbus.traffic"
#define FGSWIFTBUS_TRAFFIC_OBJECTPATH "/fgswiftbus/traffic"
//! \endcond
namespace FGSwiftBus {
/*!
* FGSwiftBus service object for traffic aircraft which is accessible through DBus
*/
class CTraffic : public CDBusObject
{
public:
//! Constructor
CTraffic();
//! Destructor
~CTraffic() override;
//! DBus interface name
static const std::string& InterfaceName();
//! DBus object path
static const std::string& ObjectPath();
//! Initialize the multiplayer planes rendering and return true if successful
bool initialize();
//! Perform generic processing
int process();
void emitSimFrame();
protected:
virtual void dbusDisconnectedHandler() override;
DBusHandlerResult dbusMessageHandler(const CDBusMessage& message) override;
private:
void emitPlaneAdded(const std::string& callsign);
void cleanup();
struct Plane {
void* id = nullptr;
std::string callsign;
char label[32]{};
};
bool m_emitSimFrame = true;
std::unique_ptr<FGSwiftAircraftManager> acm;
};
} // namespace FGSwiftBus
#endif // guard

596
src/Network/atlas.cxx Normal file
View File

@@ -0,0 +1,596 @@
// atlas.cxx -- Atlas protocal class
//
// Written by Curtis Olson, started November 1999.
//
// Copyright (C) 1999 Curtis L. Olson - http://www.flightgear.org/~curt
//
// 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 <cstdlib>
#include <cstring>
#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/globals.hxx>
#include <Main/fg_props.hxx>
#include <Main/fg_init.hxx>
#include "atlas.hxx"
FGAtlas::FGAtlas() :
length(0),
fdm(new FlightProperties)
{
_adf_freq = fgGetNode("/instrumentation/adf/frequencies/selected-khz", true);
_nav1_freq = fgGetNode("/instrumentation/nav/frequencies/selected-mhz", true);
_nav1_sel_radial = fgGetNode("/instrumentation/nav/radials/selected-deg", true);
_nav2_freq = fgGetNode("/instrumentation/nav[1]/frequencies/selected-mhz", true);
_nav2_sel_radial = fgGetNode("/instrumentation/nav[1]/radials/selected-deg", true);
}
FGAtlas::~FGAtlas() {
delete fdm;
}
// calculate the atlas check sum
static char calc_atlas_cksum(char *sentence) {
unsigned char sum = 0;
int i, len;
// cout << sentence << endl;
len = strlen(sentence);
sum = sentence[0];
for ( i = 1; i < len; i++ ) {
// cout << sentence[i];
sum ^= sentence[i];
}
// cout << endl;
// printf("sum = %02x\n", sum);
return sum;
}
// generate Atlas message
bool FGAtlas::gen_message() {
// cout << "generating atlas message" << endl;
char rmc[256], gga[256], patla[256];
char rmc_sum[10], gga_sum[10], patla_sum[10];
char dir;
int deg;
double min;
SGTime *t = globals->get_time_params();
char utc[10];
snprintf( utc, sizeof(utc), "%02d%02d%02d",
t->getGmt()->tm_hour, t->getGmt()->tm_min, t->getGmt()->tm_sec );
char lat[20];
double latd = fdm->get_Latitude() * SGD_RADIANS_TO_DEGREES;
if ( latd < 0.0 ) {
latd *= -1.0;
dir = 'S';
} else {
dir = 'N';
}
deg = (int)(latd);
min = (latd - (double)deg) * 60.0;
snprintf( lat, sizeof(lat), "%02d%06.3f,%c", abs(deg), min, dir);
char lon[20];
double lond = fdm->get_Longitude() * SGD_RADIANS_TO_DEGREES;
if ( lond < 0.0 ) {
lond *= -1.0;
dir = 'W';
} else {
dir = 'E';
}
deg = (int)(lond);
min = (lond - (double)deg) * 60.0;
snprintf( lon, sizeof(lon), "%03d%06.3f,%c", abs(deg), min, dir);
char speed[10];
snprintf( speed, sizeof(speed), "%05.1f", fdm->get_V_equiv_kts() );
char heading[10];
snprintf( heading, sizeof(heading), "%05.1f", fdm->get_Psi() * SGD_RADIANS_TO_DEGREES );
char altitude_m[10];
snprintf( altitude_m, sizeof(altitude_m), "%02d",
(int)(fdm->get_Altitude() * SG_FEET_TO_METER) );
char altitude_ft[10];
snprintf( altitude_ft, sizeof(altitude_ft), "%02d", (int)fdm->get_Altitude() );
char date[16];
unsigned short tm_mday = t->getGmt()->tm_mday;
unsigned short tm_mon = t->getGmt()->tm_mon + 1;
unsigned short tm_year = t->getGmt()->tm_year;
snprintf( date, sizeof(date), "%02u%02u%02u", tm_mday, tm_mon, tm_year);
// $GPRMC,HHMMSS,A,DDMM.MMM,N,DDDMM.MMM,W,XXX.X,XXX.X,DDMMYY,XXX.X,E*XX
snprintf( rmc, sizeof(rmc), "GPRMC,%s,A,%s,%s,%s,%s,%s,0.000,E",
utc, lat, lon, speed, heading, date );
snprintf( rmc_sum, sizeof(rmc_sum), "%02X", calc_atlas_cksum(rmc) );
snprintf( gga, sizeof(gga), "GPGGA,%s,%s,%s,1,,,%s,F,,,,",
utc, lat, lon, altitude_ft );
snprintf( gga_sum, sizeof(gga_sum), "%02X", calc_atlas_cksum(gga) );
snprintf( patla, sizeof(patla), "PATLA,%.2f,%.1f,%.2f,%.1f,%.0f",
_nav1_freq->getDoubleValue(),
_nav1_sel_radial->getDoubleValue(),
_nav2_freq->getDoubleValue(),
_nav2_sel_radial->getDoubleValue(),
_adf_freq->getDoubleValue() );
snprintf( patla_sum, sizeof(patla_sum), "%02X", calc_atlas_cksum(patla) );
SG_LOG( SG_IO, SG_DEBUG, rmc );
SG_LOG( SG_IO, SG_DEBUG, gga );
SG_LOG( SG_IO, SG_DEBUG, patla );
string atlas_sentence;
// RMC sentence
atlas_sentence = "$";
atlas_sentence += rmc;
atlas_sentence += "*";
atlas_sentence += rmc_sum;
atlas_sentence += "\n";
// GGA sentence
atlas_sentence += "$";
atlas_sentence += gga;
atlas_sentence += "*";
atlas_sentence += gga_sum;
atlas_sentence += "\n";
// PATLA sentence
atlas_sentence += "$";
atlas_sentence += patla;
atlas_sentence += "*";
atlas_sentence += patla_sum;
atlas_sentence += "\n";
// cout << atlas_sentence;
length = atlas_sentence.length();
strncpy( buf, atlas_sentence.c_str(), length );
return true;
}
// parse Atlas message. messages will look something like the
// following:
//
// $GPRMC,163227,A,3321.173,N,11039.855,W,000.1,270.0,171199,0.000,E*61
// $GPGGA,163227,3321.173,N,11039.855,W,1,,,3333,F,,,,*0F
bool FGAtlas::parse_message() {
SG_LOG( SG_IO, SG_INFO, "parse atlas message" );
string msg = buf;
msg = msg.substr( 0, length );
SG_LOG( SG_IO, SG_INFO, "entire message = " << msg );
string::size_type begin_line, end_line, begin, end;
begin_line = begin = 0;
// extract out each line
end_line = msg.find("\n", begin_line);
while ( end_line != string::npos ) {
string line = msg.substr(begin_line, end_line - begin_line);
begin_line = end_line + 1;
SG_LOG( SG_IO, SG_INFO, " input line = " << line );
// leading character
string start = msg.substr(begin, 1);
++begin;
SG_LOG( SG_IO, SG_INFO, " start = " << start );
// sentence
end = msg.find(",", begin);
if ( end == string::npos ) {
return false;
}
string sentence = msg.substr(begin, end - begin);
begin = end + 1;
SG_LOG( SG_IO, SG_INFO, " sentence = " << sentence );
double lon_deg, lon_min, lat_deg, lat_min;
double lon, lat, speed, heading, altitude;
if ( sentence == "GPRMC" ) {
// time
end = msg.find(",", begin);
if ( end == string::npos ) {
return false;
}
string utc = msg.substr(begin, end - begin);
begin = end + 1;
SG_LOG( SG_IO, SG_INFO, " utc = " << utc );
// junk
end = msg.find(",", begin);
if ( end == string::npos ) {
return false;
}
string junk = msg.substr(begin, end - begin);
begin = end + 1;
SG_LOG( SG_IO, SG_INFO, " junk = " << junk );
// lat val
end = msg.find(",", begin);
if ( end == string::npos ) {
return false;
}
string lat_str = msg.substr(begin, end - begin);
begin = end + 1;
lat_deg = atof( lat_str.substr(0, 2).c_str() );
lat_min = atof( lat_str.substr(2).c_str() );
// lat dir
end = msg.find(",", begin);
if ( end == string::npos ) {
return false;
}
string lat_dir = msg.substr(begin, end - begin);
begin = end + 1;
lat = lat_deg + ( lat_min / 60.0 );
if ( lat_dir == "S" ) {
lat *= -1;
}
fdm->set_Latitude( lat * SGD_DEGREES_TO_RADIANS );
SG_LOG( SG_IO, SG_INFO, " lat = " << lat );
// lon val
end = msg.find(",", begin);
if ( end == string::npos ) {
return false;
}
string lon_str = msg.substr(begin, end - begin);
begin = end + 1;
lon_deg = atof( lon_str.substr(0, 3).c_str() );
lon_min = atof( lon_str.substr(3).c_str() );
// lon dir
end = msg.find(",", begin);
if ( end == string::npos ) {
return false;
}
string lon_dir = msg.substr(begin, end - begin);
begin = end + 1;
lon = lon_deg + ( lon_min / 60.0 );
if ( lon_dir == "W" ) {
lon *= -1;
}
fdm->set_Longitude( lon * SGD_DEGREES_TO_RADIANS );
SG_LOG( SG_IO, SG_INFO, " lon = " << lon );
#if 0
double sl_radius, lat_geoc;
sgGeodToGeoc( fdm->get_Latitude(),
fdm->get_Altitude(),
&sl_radius, &lat_geoc );
fdm->set_Geocentric_Position( lat_geoc,
fdm->get_Longitude(),
sl_radius + fdm->get_Altitude() );
#endif
// speed
end = msg.find(",", begin);
if ( end == string::npos ) {
return false;
}
string speed_str = msg.substr(begin, end - begin);
begin = end + 1;
speed = atof( speed_str.c_str() );
fdm->set_V_calibrated_kts( speed );
// fdm->set_V_ground_speed( speed );
SG_LOG( SG_IO, SG_INFO, " speed = " << speed );
// heading
end = msg.find(",", begin);
if ( end == string::npos ) {
return false;
}
string hdg_str = msg.substr(begin, end - begin);
begin = end + 1;
heading = atof( hdg_str.c_str() );
fdm->set_Euler_Angles( fdm->get_Phi(),
fdm->get_Theta(),
heading * SGD_DEGREES_TO_RADIANS );
SG_LOG( SG_IO, SG_INFO, " heading = " << heading );
} else if ( sentence == "GPGGA" ) {
// time
end = msg.find(",", begin);
if ( end == string::npos ) {
return false;
}
string utc = msg.substr(begin, end - begin);
begin = end + 1;
SG_LOG( SG_IO, SG_INFO, " utc = " << utc );
// lat val
end = msg.find(",", begin);
if ( end == string::npos ) {
return false;
}
string lat_str = msg.substr(begin, end - begin);
begin = end + 1;
lat_deg = atof( lat_str.substr(0, 2).c_str() );
lat_min = atof( lat_str.substr(2).c_str() );
// lat dir
end = msg.find(",", begin);
if ( end == string::npos ) {
return false;
}
string lat_dir = msg.substr(begin, end - begin);
begin = end + 1;
lat = lat_deg + ( lat_min / 60.0 );
if ( lat_dir == "S" ) {
lat *= -1;
}
// fdm->set_Latitude( lat * SGD_DEGREES_TO_RADIANS );
SG_LOG( SG_IO, SG_INFO, " lat = " << lat );
// lon val
end = msg.find(",", begin);
if ( end == string::npos ) {
return false;
}
string lon_str = msg.substr(begin, end - begin);
begin = end + 1;
lon_deg = atof( lon_str.substr(0, 3).c_str() );
lon_min = atof( lon_str.substr(3).c_str() );
// lon dir
end = msg.find(",", begin);
if ( end == string::npos ) {
return false;
}
string lon_dir = msg.substr(begin, end - begin);
begin = end + 1;
lon = lon_deg + ( lon_min / 60.0 );
if ( lon_dir == "W" ) {
lon *= -1;
}
// fdm->set_Longitude( lon * SGD_DEGREES_TO_RADIANS );
SG_LOG( SG_IO, SG_INFO, " lon = " << lon );
// junk
end = msg.find(",", begin);
if ( end == string::npos ) {
return false;
}
string junk = msg.substr(begin, end - begin);
begin = end + 1;
SG_LOG( SG_IO, SG_INFO, " junk = " << junk );
// junk
end = msg.find(",", begin);
if ( end == string::npos ) {
return false;
}
junk = msg.substr(begin, end - begin);
begin = end + 1;
SG_LOG( SG_IO, SG_INFO, " junk = " << junk );
// junk
end = msg.find(",", begin);
if ( end == string::npos ) {
return false;
}
junk = msg.substr(begin, end - begin);
begin = end + 1;
SG_LOG( SG_IO, SG_INFO, " junk = " << junk );
// altitude
end = msg.find(",", begin);
if ( end == string::npos ) {
return false;
}
string alt_str = msg.substr(begin, end - begin);
altitude = atof( alt_str.c_str() );
begin = end + 1;
// altitude units
end = msg.find(",", begin);
if ( end == string::npos ) {
return false;
}
string alt_units = msg.substr(begin, end - begin);
begin = end + 1;
if ( alt_units != "F" ) {
altitude *= SG_METER_TO_FEET;
}
fdm->set_Altitude( altitude );
SG_LOG( SG_IO, SG_INFO, " altitude = " << altitude );
} else if ( sentence == "PATLA" ) {
// nav1 freq
end = msg.find(",", begin);
if ( end == string::npos ) {
return false;
}
string nav1_freq = msg.substr(begin, end - begin);
begin = end + 1;
SG_LOG( SG_IO, SG_INFO, " nav1_freq = " << nav1_freq );
// nav1 selected radial
end = msg.find(",", begin);
if ( end == string::npos ) {
return false;
}
string nav1_rad = msg.substr(begin, end - begin);
begin = end + 1;
SG_LOG( SG_IO, SG_INFO, " nav1_rad = " << nav1_rad );
// nav2 freq
end = msg.find(",", begin);
if ( end == string::npos ) {
return false;
}
string nav2_freq = msg.substr(begin, end - begin);
begin = end + 1;
SG_LOG( SG_IO, SG_INFO, " nav2_freq = " << nav2_freq );
// nav2 selected radial
end = msg.find(",", begin);
if ( end == string::npos ) {
return false;
}
string nav2_rad = msg.substr(begin, end - begin);
begin = end + 1;
SG_LOG( SG_IO, SG_INFO, " nav2_rad = " << nav2_rad );
// adf freq
end = msg.find("*", begin);
if ( end == string::npos ) {
return false;
}
string adf_freq = msg.substr(begin, end - begin);
begin = end + 1;
SG_LOG( SG_IO, SG_INFO, " adf_freq = " << adf_freq );
}
// printf("%.8f %.8f\n", lon, lat);
begin = begin_line;
end_line = msg.find("\n", begin_line);
}
return true;
}
// open hailing frequencies
bool FGAtlas::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 FGAtlas::process() {
SGIOChannel *io = get_io_channel();
if ( get_direction() == SG_IO_OUT ) {
gen_message();
if ( ! io->write( buf, length ) ) {
SG_LOG( SG_IO, SG_WARN, "Error writing data." );
return false;
}
} else if ( get_direction() == SG_IO_IN ) {
if ( (length = io->readline( buf, FG_MAX_MSG_SIZE )) > 0 ) {
parse_message();
} else {
SG_LOG( SG_IO, SG_WARN, "Error reading data." );
return false;
}
if ( (length = io->readline( buf, FG_MAX_MSG_SIZE )) > 0 ) {
parse_message();
} else {
SG_LOG( SG_IO, SG_WARN, "Error reading data." );
return false;
}
}
return true;
}
// close the channel
bool FGAtlas::close() {
SG_LOG( SG_IO, SG_INFO, "closing FGAtlas" );
SGIOChannel *io = get_io_channel();
set_enabled( false );
if ( ! io->close() ) {
return false;
}
return true;
}

65
src/Network/atlas.hxx Normal file
View File

@@ -0,0 +1,65 @@
// atlas.hxx -- Atlas protocal class
//
// Written by Curtis Olson, started November 1999.
//
// Copyright (C) 1999 Curtis L. Olson - http://www.flightgear.org/~curt
//
// 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$
#ifndef _FG_ATLAS_HXX
#define _FG_ATLAS_HXX
#include <simgear/compiler.h>
#include <string>
#include "protocol.hxx"
#include <simgear/props/props.hxx>
class FlightProperties;
class FGAtlas : public FGProtocol {
char buf[ FG_MAX_MSG_SIZE ];
int length;
FlightProperties* fdm;
SGPropertyNode_ptr _adf_freq, _nav1_freq,_nav1_sel_radial, _nav2_freq, _nav2_sel_radial;
public:
FGAtlas();
~FGAtlas();
bool gen_message();
bool parse_message();
// open hailing frequencies
bool open();
// process work for this port
bool process();
// close the channel
bool close();
};
#endif // _FG_ATLAS_HXX

191
src/Network/dds_props.cxx Normal file
View File

@@ -0,0 +1,191 @@
// dds_props.cxx -- FGFS "DDS" properties protocal class
//
// Written by Erik Hofman, started April 2021
//
// Copyright (C) 2021 by Erik Hofman <erik@ehofman.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 <simgear/structure/exception.hxx>
#include <simgear/debug/logstream.hxx>
#include <simgear/io/iochannel.hxx>
#include <simgear/props/props.hxx>
#include <simgear/io/SGDataDistributionService.hxx>
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
#include "dds_props.hxx"
// open hailing frequencies
bool FGDDSProps::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();
SG_DDS_Topic *dds = static_cast<SG_DDS_Topic*>(io);
dds->setup(prop, &FG_DDS_prop_desc);
// always send and recieve.
if (!io->open(SG_IO_BI)) {
SG_LOG(SG_IO, SG_ALERT, "Error opening channel communication layer.");
return false;
}
set_enabled(true);
return true;
}
// process work for this port
bool FGDDSProps::process() {
SGIOChannel *io = get_io_channel();
int length = sizeof(prop);
char *buf = reinterpret_cast<char*>(&prop);
if (get_direction() == SG_IO_IN)
{
// act as a client: send a request and wait for an answer.
}
else if (get_direction() == SG_IO_OUT || get_direction() == SG_IO_BI)
{
// act as a server: read requests and send the results.
while (io->read(buf, length) &&
prop.version == FG_DDS_PROP_VERSION &&
prop.mode == FG_DDS_MODE_READ)
{
// s is used to keep a copy of the string returend by
// p->getStringValue() in setProp until it is sent to the DDS layer.
std::string s;
if (prop.id == FG_DDS_PROP_REQUEST)
{
if (prop.val._d == FG_DDS_STRING)
{
const char *path = prop.val._u.String;
auto it = path_list.find(path);
if (it == path_list.end())
{
SGPropertyNode_ptr props = globals->get_props();
SGPropertyNode_ptr p = props->getNode(path);
if (p)
{
prop.id = prop_list.size();
try {
prop_list.push_back(p);
path_list[prop.val._u.String] = prop.id;
} catch (sg_exception&) {
SG_LOG(SG_IO, SG_ALERT, "out of memory");
}
}
setProp(prop, p, s);
}
else
{
prop.id = std::distance(path_list.begin(), it);
setProp(prop, prop_list[prop.id], s);
}
}
else
{
SG_LOG(SG_IO, SG_DEBUG, "Recieved a mangled DDS sample.");
setProp(prop, nullptr, s);
}
}
else {
setProp(prop, prop_list[prop.id], s);
}
// send the response.
if (!io->write(buf, length)) {
SG_LOG(SG_IO, SG_ALERT, "Error writing data.");
}
} // while
}
return true;
}
// close the channel
bool FGDDSProps::close() {
SGIOChannel *io = get_io_channel();
set_enabled(false);
if (! io->close()) {
return false;
}
return true;
}
void FGDDSProps::setProp(FG_DDS_prop& prop, SGPropertyNode_ptr p, std::string& s)
{
// prop.id = FG_DDS_PROP_REQUEST;
prop.version = FG_DDS_PROP_VERSION;
prop.mode = FG_DDS_MODE_WRITE;
if (p)
{
simgear::props::Type type = p->getType();
if (type == simgear::props::BOOL) {
prop.val._d = FG_DDS_BOOL;
prop.val._u.Bool = p->getBoolValue();
} else if (type == simgear::props::INT) {
prop.val._d = FG_DDS_INT;
prop.val._u.Int32 = p->getIntValue();
} else if (type == simgear::props::LONG) {
prop.val._d = FG_DDS_LONG;
prop.val._u.Int64 = p->getLongValue();
} else if (type == simgear::props::FLOAT) {
prop.val._d = FG_DDS_FLOAT;
prop.val._u.Float32 = p->getFloatValue();
} else if (type == simgear::props::DOUBLE) {
prop.val._d = FG_DDS_DOUBLE;
prop.val._u.Float64 = p->getDoubleValue();
} else if (type == simgear::props::ALIAS) {
prop.val._d = FG_DDS_ALIAS;
s = p->getStringValue();
prop.val._u.String = const_cast<char*>(s.c_str());
} else if (type == simgear::props::STRING) {
prop.val._d = FG_DDS_STRING;
s = p->getStringValue();
prop.val._u.String = const_cast<char*>(s.c_str());
} else if (type == simgear::props::UNSPECIFIED) {
prop.val._d = FG_DDS_UNSPECIFIED;
s = p->getStringValue();
prop.val._u.String = const_cast<char*>(s.c_str());
} else {
prop.val._d = FG_DDS_NONE;
prop.val._u.Int32 = 0;
}
}
}

59
src/Network/dds_props.hxx Normal file
View File

@@ -0,0 +1,59 @@
// dds_props.hxx -- FGFS "DDS" properties protocal class
//
// Written by Erik Hofman, started April 2021
//
// Copyright (C) 2021 by Erik Hofman <erik@ehofman.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$
#pragma once
#include <string>
#include <map>
#include <simgear/compiler.h>
#include <simgear/props/propsfwd.hxx>
#include "protocol.hxx"
#include "DDS/dds_props.h"
class FGDDSProps : public FGProtocol {
FG_DDS_prop prop;
simgear::PropertyList prop_list;
std::map<std::string,uint32_t> path_list;
static void setProp(FG_DDS_prop&, SGPropertyNode_ptr, std::string &);
public:
FGDDSProps() = default;
~FGDDSProps() = default;
// open hailing frequencies
bool open();
// process work for this port
bool process();
// close the channel
bool close();
};

748
src/Network/fgcom.cxx Normal file
View File

@@ -0,0 +1,748 @@
// fgcom.cxx -- FGCom: Voice communication
//
// Written by Clement de l'Hamaide, started May 2013.
//
// 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.
# include <config.h>
#include "fgcom.hxx"
// standard library includes
#include <cstdio>
// simgear includes
#include <simgear/compiler.h>
#include <simgear/sg_inlines.h>
#include <simgear/debug/logstream.hxx>
#include <simgear/math/sg_geodesy.hxx>
#include <simgear/timing/timestamp.hxx>
// flightgear includes
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
#include <ATC/CommStation.hxx>
#include <Airports/airport.hxx>
#include <Navaids/navlist.hxx>
#include <Main/sentryIntegration.hxx>
#include <iaxclient.h>
#define NUM_CALLS 4
#define MAX_GND_RANGE 10.0
#define MAX_TWR_RANGE 50.0
#define MAX_RANGE 100.0
#define MIN_RANGE 20.0
#define MIN_GNDTWR_RANGE 0.0
#define DEFAULT_SERVER "fgcom.flightgear.org"
#define IAX_DELAY 300 // delay between calls in milliseconds
#define TEST_FREQ 910.00
#define NULL_ICAO "ZZZZ"
const int special_freq[] = { // Define some freq who need to be used with NULL_ICAO
910000,
911000,
700000,
123450,
122750,
121500,
123500 };
static FGCom* static_instance = NULL;
static int iaxc_callback( iaxc_event e )
{
switch( e.type )
{
case IAXC_EVENT_TEXT:
static_instance->iaxTextEvent(e.ev.text);
break;
default:
return 0;
}
return 1;
}
void FGCom::iaxTextEvent(struct iaxc_ev_text text)
{
if( (text.type == IAXC_TEXT_TYPE_STATUS ||
text.type == IAXC_TEXT_TYPE_IAX) &&
_showMessages_node->getBoolValue() )
{
_text_node->setStringValue(text.message);
}
auto level = SG_INFO;
if ((text.type == IAXC_TEXT_TYPE_ERROR) || (text.type == IAXC_TEXT_TYPE_FATALERROR)) {
level = SG_ALERT;
}
SG_LOG(SG_SOUND, level, std::string{"FCCom IAX:"} + text.message);
}
FGCom::FGCom()
{
_maxRange = MAX_RANGE;
_minRange = MIN_RANGE;
}
FGCom::~FGCom()
{
}
void FGCom::bind()
{
SGPropertyNode *node = fgGetNode("/sim/fgcom", 0, true);
_test_node = node->getChild( "test", 0, true );
_server_node = node->getChild( "server", 0, true );
_enabled_node = node->getChild( "enabled", 0, true );
_micBoost_node = node->getChild( "mic-boost", 0, true );
_micLevel_node = node->getChild( "mic-level", 0, true );
_silenceThd_node = node->getChild( "silence-threshold", 0, true );
_speakerLevel_node = node->getChild( "speaker-level", 0, true );
_selectedInput_node = node->getChild( "device-input", 0, true );
_selectedOutput_node = node->getChild( "device-output", 0, true );
_showMessages_node = node->getChild( "show-messages", 0, true );
SGPropertyNode *reg_node = node->getChild("register", 0, true);
_register_node = reg_node->getChild( "enabled", 0, true );
_username_node = reg_node->getChild( "username", 0, true );
_password_node = reg_node->getChild( "password", 0, true );
_ptt_node = fgGetNode("/controls/radios/comm-ptt", true);
_selected_comm_node = fgGetNode("/controls/radios/comm-radio-selected", true);
_callsign_node = fgGetNode("/sim/multiplay/callsign", true);
_text_node = fgGetNode("/sim/messages/atc", true );
_version_node = fgGetNode("/sim/version/flightgear", true );
// Set default values if not provided
if ( !_enabled_node->hasValue() )
_enabled_node->setBoolValue(true);
if ( !_test_node->hasValue() )
_test_node->setBoolValue(false);
if ( !_micBoost_node->hasValue() )
_micBoost_node->setIntValue(1);
if ( !_server_node->hasValue() )
_server_node->setStringValue(DEFAULT_SERVER);
if ( !_speakerLevel_node->hasValue() )
_speakerLevel_node->setFloatValue(1.0);
if ( !_micLevel_node->hasValue() )
_micLevel_node->setFloatValue(1.0);
if ( !_silenceThd_node->hasValue() )
_silenceThd_node->setFloatValue(-35.0);
if ( !_register_node->hasValue() )
_register_node->setBoolValue(false);
if ( !_username_node->hasValue() )
_username_node->setStringValue("guest");
if ( !_password_node->hasValue() )
_password_node->setStringValue("guest");
if ( !_showMessages_node->hasValue() )
_showMessages_node->setBoolValue(false);
_mpTransmitFrequencyNode = fgGetNode("sim/multiplay/comm-transmit-frequency-hz", 0, true);
_mpTransmitPowerNode = fgGetNode("sim/multiplay/comm-transmit-power-norm", 0, true);
_selectedOutput_node->addChangeListener(this);
_selectedInput_node->addChangeListener(this);
_speakerLevel_node->addChangeListener(this);
_silenceThd_node->addChangeListener(this);
_micBoost_node->addChangeListener(this);
_micLevel_node->addChangeListener(this);
_enabled_node->addChangeListener(this);
_ptt_node->addChangeListener(this);
_selected_comm_node->addChangeListener(this);
_test_node->addChangeListener(this);
// if you listen to more properties, be sure to remove
// the listener in unbind() as well
}
void FGCom::unbind()
{
_selectedOutput_node->removeChangeListener(this);
_selectedInput_node->removeChangeListener(this);
_speakerLevel_node->removeChangeListener(this);
_silenceThd_node->removeChangeListener(this);
_micBoost_node->removeChangeListener(this);
_micLevel_node->removeChangeListener(this);
_enabled_node->removeChangeListener(this);
_ptt_node->removeChangeListener(this);
_selected_comm_node->removeChangeListener(this);
_test_node->removeChangeListener(this);
}
void FGCom::init()
{
_enabled = _enabled_node->getBoolValue();
_server = _server_node->getStringValue();
_register = _register_node->getBoolValue();
_username = _username_node->getStringValue();
_password = _password_node->getStringValue();
_currentCommFrequency = 0.0;
_maxRange = MAX_RANGE;
_minRange = MIN_RANGE;
}
void FGCom::postinit()
{
if( !_enabled ) {
return;
}
//WARNING: this _must_ be executed after sound system is totally initialized !
if( iaxc_initialize(NUM_CALLS) ) {
SG_LOG(SG_IO, SG_ALERT, "FGCom: cannot initialize iaxclient");
_enabled = false;
return;
}
assert( static_instance == NULL );
static_instance = this;
iaxc_set_event_callback( iaxc_callback );
// FIXME: To be implemented in IAX audio driver
//iaxc_mic_boost_set( _micBoost_node->getIntValue() );
std::string app = "FGFS-";
app += _version_node->getStringValue();
iaxc_set_callerid( _callsign_node->getStringValue().c_str(), app.c_str() );
iaxc_set_formats (IAXC_FORMAT_SPEEX, IAXC_FORMAT_ULAW|IAXC_FORMAT_SPEEX);
iaxc_set_speex_settings(1, 5, 0, 1, 0, 3);
iaxc_set_filters(IAXC_FILTER_AGC | IAXC_FILTER_DENOISE);
iaxc_set_silence_threshold(_silenceThd_node->getFloatValue());
iaxc_start_processing_thread ();
// Now IAXClient is initialized
_initialized = true;
if ( _register ) {
_regId = iaxc_register( const_cast<char*>(_username.c_str()),
const_cast<char*>(_password.c_str()),
const_cast<char*>(_server.c_str()) );
if( _regId == -1 ) {
SG_LOG(SG_IO, SG_ALERT, "FGCom: cannot register iaxclient");
return;
}
}
/*
Here we will create the list of available audio devices
Each audio device has a name, an ID, and a list of capabilities
If an audio device can output sound, available-output=true
If an audio device can input sound, available-input=true
/sim/fgcom/selected-input (int)
/sim/fgcom/selected-output (int)
/sim/fgcom/device[n]/id (int)
/sim/fgcom/device[n]/name (string)
/sim/fgcom/device[n]/available-input (bool)
/sim/fgcom/device[n]/available-output (bool)
*/
//FIXME: OpenAL driver use an hard-coded device
// so all following is unused finally until someone
// implement "multi-device" support in IAX audio driver
SGPropertyNode *node = fgGetNode("/sim/fgcom", 0, true);
struct iaxc_audio_device *devs;
int nDevs, input, output, ring;
iaxc_audio_devices_get(&devs,&nDevs, &input, &output, &ring);
for(int i=0; i<nDevs; i++ ) {
SGPropertyNode *in_node = node->getChild("device", i, true);
// devID
_deviceID_node[i] = in_node->getChild("id", 0, true);
_deviceID_node[i]->setIntValue(devs[i].devID);
// name
_deviceName_node[i] = in_node->getChild("name", 0, true);
_deviceName_node[i]->setStringValue(devs[i].name);
// input capability
_deviceInput_node[i] = in_node->getChild("available-input", 0, true);
if( devs[i].capabilities & IAXC_AD_INPUT )
_deviceInput_node[i]->setBoolValue(true);
else
_deviceInput_node[i]->setBoolValue(false);
// output capability
_deviceOutput_node[i] = in_node->getChild("available-output", 0, true);
if( devs[i].capabilities & IAXC_AD_OUTPUT )
_deviceOutput_node[i]->setBoolValue(true);
else
_deviceOutput_node[i]->setBoolValue(false);
// use default device at start
if( devs[i].capabilities & IAXC_AD_INPUT_DEFAULT )
_selectedInput_node->setIntValue(devs[i].devID);
if( devs[i].capabilities & IAXC_AD_OUTPUT_DEFAULT )
_selectedOutput_node->setIntValue(devs[i].devID);
}
// Mute the mic and set speaker at start
iaxc_input_level_set( 0.0 );
iaxc_output_level_set(getCurrentCommVolume());
iaxc_millisleep(50);
// Do the first call at start
setupCommFrequency();
connectToCommFrequency();
}
double FGCom::getCurrentCommVolume() const {
double rv = 1.0;
if (_speakerLevel_node)
rv = _speakerLevel_node->getFloatValue();
if (_commVolumeNode)
rv = rv * _commVolumeNode->getFloatValue();
return rv;
}
double FGCom::getCurrentFrequencyKhz() const {
return 10 * static_cast<int>(_currentCommFrequency * 100 + 0.25);
}
void FGCom::setupCommFrequency(int channel) {
if (channel < 1) {
if (_selected_comm_node != nullptr) {
channel = _selected_comm_node->getIntValue();
}
}
// disconnect if channel set to 0
if (channel < 1) {
if (_currentCallIdent != -1) {
iaxc_dump_call_number(_currentCallIdent);
SG_LOG(SG_IO, SG_INFO, "FGCom: disconnect as channel 0 " << _currentCallIdent);
_currentCallIdent = -1;
}
_currentCommFrequency = 0.0;
return;
}
if (channel > 0)
{
channel--; // adjust back to zero based index.
SGPropertyNode *commRadioNode = fgGetNode("/instrumentation/")->getChild("comm", channel, false);
if (commRadioNode) {
SGPropertyNode *frequencyNode = commRadioNode->getChild("frequencies");
if (_commVolumeNode)
_commVolumeNode->removeChangeListener(this);
_commVolumeNode = commRadioNode->getChild("volume");
if (frequencyNode) {
frequencyNode = frequencyNode->getChild("selected-mhz");
if (frequencyNode) {
if (_commFrequencyNode != frequencyNode) {
if (_commFrequencyNode)
_commFrequencyNode->removeChangeListener(this);
_commFrequencyNode = frequencyNode;
_commFrequencyNode->addChangeListener(this);
_commVolumeNode->addChangeListener(this);
}
_currentCommFrequency = frequencyNode->getDoubleValue();
return;
}
}
}
SG_LOG(SG_IO, SG_INFO, "FGCom: setupCommFrequency node listener failed: channel " << channel);
}
if (_commFrequencyNode)
_commFrequencyNode->removeChangeListener(this);
SG_LOG(SG_IO, SG_INFO, "FGCom: setupCommFrequency invalid channel " << channel);
_currentCommFrequency = 0.0;
}
void FGCom::connectToCommFrequency() {
// ensure that the current comm is still in range
if ((_currentCallFrequency > 0.0) && !isInRange(_currentCallFrequency)) {
SG_LOG(SG_IO, SG_WARN, "FGCom: call out of range of: " << _currentCallFrequency);
_currentCallFrequency = 0.0;
}
// don't connected (and disconnect if already connected) when tuned freq is 0
if (_currentCommFrequency < 1.0) {
if (_currentCallIdent != -1) {
iaxc_dump_call_number(_currentCallIdent);
SG_LOG(SG_IO, SG_INFO, "FGCom: disconnect as freq 0: current call " << _currentCallIdent);
_currentCallIdent = -1;
}
return;
}
if (_currentCallFrequency != _currentCommFrequency || _currentCallIdent == -1) {
if (_currentCallIdent != -1) {
iaxc_dump_call_number(_currentCallIdent);
SG_LOG(SG_IO, SG_INFO, "FGCom: dump_call_number " << _currentCallIdent);
_currentCallIdent = -1;
}
if (_currentCallIdent == -1)
{
std::string num = computePhoneNumber(_currentCommFrequency, getAirportCode(_currentCommFrequency));
_processingTimer.stamp();
if (!isInRange(_currentCommFrequency)) {
if (_currentCallIdent != -1) {
SG_LOG(SG_IO, SG_INFO, "FGCom: disconnect call as not in range " << _currentCallIdent);
if (_currentCallIdent != -1) {
iaxc_dump_call_number(_currentCallIdent);
_currentCallIdent = -1;
}
}
return;
}
if (!num.empty()) {
_currentCallIdent = iaxc_call(num.c_str());
if (_currentCallIdent == -1)
SG_LOG(SG_IO, SG_DEBUG, "FGCom: cannot call " << num.c_str());
else {
SG_LOG(SG_IO, SG_DEBUG, "FGCom: call established " << num.c_str() << " Freq: " << _currentCommFrequency);
_currentCallFrequency = _currentCommFrequency;
}
}
else
SG_LOG(SG_IO, SG_WARN, "FGCom: frequency " << _currentCommFrequency << " does not map to valid IAX address");
}
}
}
void FGCom::updateCall()
{
if (_processingTimer.elapsedMSec() > IAX_DELAY) {
_processingTimer.stamp();
connectToCommFrequency();
}
}
void FGCom::update(double dt)
{
if (!_enabled || !_initialized) {
return;
}
updateCall();
}
void FGCom::shutdown()
{
if( !_enabled ) {
return;
}
_initialized = false;
_enabled = false;
iaxc_set_event_callback(NULL);
iaxc_unregister(_regId);
iaxc_stop_processing_thread();
iaxc_shutdown();
// added to help debugging lingering IAX thread after fgOSCloseWindow
flightgear::addSentryBreadcrumb("Did shutdown FGCom", "info");
assert( static_instance == this );
static_instance = NULL;
}
void FGCom::valueChanged(SGPropertyNode *prop)
{
if (prop == _enabled_node) {
bool isEnabled = prop->getBoolValue();
if (_enabled == isEnabled) {
return;
}
if( isEnabled ) {
init();
postinit();
} else {
shutdown();
}
return;
}
// avoid crash when properties change before FGCom::postinit
// https://sourceforge.net/p/flightgear/codetickets/2574/
if (!_initialized) {
return;
}
if (prop == _commVolumeNode && _enabled) {
if (_ptt_node->getIntValue()) {
SG_LOG(SG_IO, SG_INFO, "FGCom: ignoring change comm volume as PTT pressed");
}
else
{
iaxc_input_level_set(0.0);
iaxc_output_level_set(getCurrentCommVolume());
SG_LOG(SG_IO, SG_INFO, "FGCom: change comm volume=" << _commVolumeNode->getFloatValue());
}
}
if (prop == _selected_comm_node && _enabled) {
setupCommFrequency();
SG_LOG(SG_IO, SG_INFO, "FGCom: change comm frequency (selected node): set to " << _currentCommFrequency);
}
if (prop == _commFrequencyNode && _enabled) {
setupCommFrequency();
SG_LOG(SG_IO, SG_INFO, "FGCom: change comm frequency (property updated): set to " << _currentCommFrequency);
}
if (prop == _ptt_node && _enabled) {
if (_ptt_node->getIntValue()) {
// ensure that we are on the right channel by calling setupCommFrequency
setupCommFrequency();
iaxc_output_level_set(0.0);
iaxc_input_level_set(_micLevel_node->getFloatValue()); //0.0 = min , 1.0 = max
_mpTransmitFrequencyNode->setValue(_currentCallFrequency * 1000000);
_mpTransmitPowerNode->setValue(1.0);
SG_LOG(SG_IO, SG_INFO, "FGCom: PTT active: " << _currentCallFrequency);
}
else {
iaxc_output_level_set(getCurrentCommVolume());
iaxc_input_level_set(0.0);
SG_LOG(SG_IO, SG_INFO, "FGCom: PTT release: " << _currentCallFrequency << " vol=" << getCurrentCommVolume());
_mpTransmitFrequencyNode->setValue(0);
_mpTransmitPowerNode->setValue(0);
}
}
if (prop == _test_node) {
testMode( prop->getBoolValue() );
return;
}
if (prop == _silenceThd_node && _initialized) {
float silenceThd = prop->getFloatValue();
SG_CLAMP_RANGE<float>( silenceThd, -60, 0 );
iaxc_set_silence_threshold( silenceThd );
return;
}
//FIXME: not implemented in IAX audio driver (audio_openal.c)
if (prop == _micBoost_node && _initialized) {
int micBoost = prop->getIntValue();
SG_CLAMP_RANGE<int>( micBoost, 0, 1 );
iaxc_mic_boost_set( micBoost ) ; // 0 = enabled , 1 = disabled
return;
}
//FIXME: not implemented in IAX audio driver (audio_openal.c)
if ((prop == _selectedInput_node || prop == _selectedOutput_node) && _initialized) {
int selectedInput = _selectedInput_node->getIntValue();
int selectedOutput = _selectedOutput_node->getIntValue();
iaxc_audio_devices_set(selectedInput, selectedOutput, 0);
return;
}
if (_listener_active)
return;
_listener_active++;
if (prop == _speakerLevel_node && _enabled) {
float speakerLevel = prop->getFloatValue();
SG_CLAMP_RANGE<float>( speakerLevel, 0.0, 1.0 );
_speakerLevel_node->setFloatValue(speakerLevel);
iaxc_output_level_set(speakerLevel);
}
if (prop == _micLevel_node && _enabled) {
float micLevel = prop->getFloatValue();
SG_CLAMP_RANGE<float>( micLevel, 0.0, 1.0 );
_micLevel_node->setFloatValue(micLevel);
//iaxc_input_level_set(micLevel);
}
_listener_active--;
}
void FGCom::testMode(bool testMode)
{
if(testMode && _initialized) {
_enabled = false;
iaxc_dump_all_calls();
iaxc_input_level_set( 1.0 );
iaxc_output_level_set( _speakerLevel_node->getFloatValue() );
std::string num = computePhoneNumber(TEST_FREQ, NULL_ICAO);
if( num.size() > 0 ) {
iaxc_millisleep(IAX_DELAY);
_currentCallIdent = iaxc_call(num.c_str());
}
if( _currentCallIdent == -1 )
SG_LOG( SG_IO, SG_DEBUG, "FGCom: cannot call " << num.c_str() );
} else {
if( _initialized ) {
iaxc_dump_all_calls();
iaxc_millisleep(IAX_DELAY);
iaxc_input_level_set( 0.0 );
iaxc_output_level_set(getCurrentCommVolume());
_currentCallIdent = -1;
_enabled = true;
}
}
}
/*
\param freq The requested frequency e.g 120.825
\return The ICAO code as string e.g LFMV
*/
std::string FGCom::getAirportCode(const double& freq)
{
SGGeod aircraftPos = globals->get_aircraft_position();
for(size_t i=0; i<sizeof(special_freq)/sizeof(special_freq[0]); i++) { // Check if it's a special freq
if(special_freq[i] == getCurrentFrequencyKhz()) {
return NULL_ICAO;
}
}
flightgear::CommStation* apt = flightgear::CommStation::findByFreq(getCurrentFrequencyKhz(), aircraftPos);
if( !apt ) {
apt = flightgear::CommStation::findByFreq(getCurrentFrequencyKhz() -10, aircraftPos); // Check for 8.33KHz
if( !apt ) {
return std::string();
}
}
if( apt->type() == FGPositioned::FREQ_TOWER ) {
_maxRange = MAX_TWR_RANGE;
_minRange = MIN_GNDTWR_RANGE;
} else if( apt->type() == FGPositioned::FREQ_GROUND ) {
_maxRange = MAX_GND_RANGE;
_minRange = MIN_GNDTWR_RANGE;
} else {
_maxRange = MAX_RANGE;
_minRange = MIN_RANGE;
}
_aptPos = apt->geod();
return apt->airport()->ident();
}
/*
\param freq The requested frequency e.g 120.825
\param iaco The associated ICAO code e.g LFMV
\return The phone number as string i.e username:password@fgcom.flightgear.org/0176707786120825
*/
std::string FGCom::computePhoneNumber(const double& freq, const std::string& icao) const
{
if( icao.empty() )
return std::string();
char phoneNumber[256];
char exten[32];
char tmp[5];
/*Convert ICAO to ASCII */
sprintf( tmp, "%4s", icao.c_str() );
/*Built the phone number */
sprintf( exten,
"%02d%02d%02d%02d%02d%06d",
01,
tmp[0],
tmp[1],
tmp[2],
tmp[3],
(int) (freq * 1000 + 0.5) );
exten[16] = '\0';
snprintf( phoneNumber,
sizeof (phoneNumber),
"%s:%s@%s/%s",
_username.c_str(),
_password.c_str(),
_server.c_str(),
exten);
return phoneNumber;
}
/*
\return A boolean value, 1=in range, 0=out of range
*/
bool FGCom::isInRange(const double &freq) const
{
for(size_t i=0; i<sizeof(special_freq)/sizeof(special_freq[0]); i++) { // Check if it's a special freq
if( (special_freq[i]) == getCurrentFrequencyKhz()) {
return 1;
}
}
SGGeod acftPos = globals->get_aircraft_position();
double distNm = SGGeodesy::distanceNm(_aptPos, acftPos);
double delta_elevation_ft = fabs(acftPos.getElevationFt() - _aptPos.getElevationFt());
double rangeNm = 1.23 * sqrt(delta_elevation_ft);
if (rangeNm > _maxRange) rangeNm = _maxRange;
if (rangeNm < _minRange) rangeNm = _minRange;
if( distNm > rangeNm ) return 0;
return 1;
}
// Register the subsystem.
SGSubsystemMgr::Registrant<FGCom> registrantFGCom;

108
src/Network/fgcom.hxx Normal file
View File

@@ -0,0 +1,108 @@
#ifndef FG_FGCOM_HXX
#define FG_FGCOM_HXX
// fgcom.hxx -- FGCom: Voice communication
//
// Written by Clement de l'Hamaide, started May 2013.
//
// 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.
#include <simgear/structure/subsystem_mgr.hxx>
#include <simgear/props/props.hxx>
#include <simgear/math/sg_geodesy.hxx>
class FGCom : public SGSubsystem,
public SGPropertyChangeListener
{
public:
FGCom();
virtual ~FGCom();
// Subsystem API.
void bind() override;
void init() override;
void postinit() override;
void shutdown() override;
void unbind() override;
void update(double dt) override;
// Subsystem identification.
static const char* staticSubsystemClassId() { return "fgcom"; }
virtual void valueChanged(SGPropertyNode *prop);
void iaxTextEvent(struct iaxc_ev_text text);
private:
SGPropertyNode_ptr _ptt_node; // PTT; nonzero int indicating channel number (instrumentation/comm/[channel-1])
SGPropertyNode_ptr _selected_comm_node; // selected channel (fgcom); nonzero channel int indicating channel number (instrumentation/comm/[channel-1])
SGPropertyNode_ptr _commFrequencyNode; // current comm node in use; e.g. /instrumentation/comm[0]
SGPropertyNode_ptr _commVolumeNode; // current volume node in use; e.g. /instrumentation/comm[0]/volume
SGPropertyNode_ptr _test_node; // sim/fgcom/test
SGPropertyNode_ptr _text_node; // sim/fgcom/text
SGPropertyNode_ptr _server_node; // sim/fgcom/server
SGPropertyNode_ptr _enabled_node; // sim/fgcom/enabled
SGPropertyNode_ptr _version_node; // sim/version/flightgear
SGPropertyNode_ptr _micBoost_node; // sim/fgcom/mic-boost
SGPropertyNode_ptr _callsign_node; // sim/multiplay/callsign
SGPropertyNode_ptr _register_node; // sim/fgcom/register/enabled
SGPropertyNode_ptr _username_node; // sim/fgcom/register/username
SGPropertyNode_ptr _password_node; // sim/fgcom/register/password
SGPropertyNode_ptr _micLevel_node; // sim/fgcom/mic-level
SGPropertyNode_ptr _silenceThd_node; // sim/fgcom/silence-threshold
SGPropertyNode_ptr _speakerLevel_node; // sim/fgcom/speaker-level
SGPropertyNode_ptr _deviceID_node[4]; // sim/fgcom/device[n]/id
SGPropertyNode_ptr _deviceName_node[4]; // sim/fgcom/device[n]/name
SGPropertyNode_ptr _deviceInput_node[4]; // sim/fgcom/device[n]/available-input
SGPropertyNode_ptr _deviceOutput_node[4]; // sim/fgcom/device[n]/available-output
SGPropertyNode_ptr _selectedInput_node; // sim/fgcom/device-input
SGPropertyNode_ptr _selectedOutput_node; // sim/fgcom/device-output
SGPropertyNode_ptr _showMessages_node; // sim/fgcom/show-messages
SGPropertyNode_ptr _mpTransmitFrequencyNode; // sim/multiplay/comm-transmit-frequency-mhz
SGPropertyNode_ptr _mpTransmitPowerNode; // sim/multiplay/comm-transmit-power-norm
double _maxRange = 0.0;
double _minRange = 0.0;
double _currentCommFrequency = 0.0;
double _currentCallFrequency = 0.0;
bool _register = true;
bool _enabled = false;
bool _initialized = false;
int _regId = 0;
int _currentCallIdent = -1;
//int _callComm1;
int _listener_active = 0;
std::string _server;
std::string _callsign;
std::string _username;
std::string _password;
SGTimeStamp _processingTimer;
SGGeod _aptPos;
std::string computePhoneNumber(const double& freq, const std::string& icao) const;
std::string getAirportCode(const double& freq);
// SGGeod getAirportPos(const double& freq) const;
void setupCommFrequency(int channel = -1);
double getCurrentFrequencyKhz() const;
double getCurrentCommVolume() const;
bool isInRange(const double& freq) const;
void updateCall();
void connectToCommFrequency();
void testMode(bool testMode);
};
#endif // of FG_FGCOM_HXX

469
src/Network/flarm.cxx Normal file
View File

@@ -0,0 +1,469 @@
// flarm.cxx -- Flarm protocol class
//
// Written by Thorsten Brehm, started November 2017.
//
// Copyright (C) 2017 Thorsten Brehm - brehmt (at) gmail 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$
#include "config.h"
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <simgear/debug/logstream.hxx>
#include <simgear/constants.h>
#include <simgear/io/iochannel.hxx>
#include <simgear/math/sg_geodesy.hxx>
#include <simgear/sg_inlines.h>
#include <FDM/flightProperties.hxx>
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
#include <flightgearBuildId.h>
#include "flarm.hxx"
using namespace NMEA;
// #define FLARM_DEBUGGING
#ifdef FLARM_DEBUGGING
#warning Flarm debugging is enabled!
#endif
/*
* The Flarm protocol emulation reports multi-player and AI aircraft within the
* configured range using NMEA-style messages. The module supports bidirectional
* communication, i.e. its capable of sending messages, and also of receiving
* (and replying to) configuration commands. The emulation should be good enough
* to convince standard NAV/moving map clients (tablet APPs like skydemon, skymap,
* xcsoar etc) that they're connected to a Flarm device. The clients receive the
* GPS position and traffic information from the flight simulator.
*
* By default, the emulation reports all moving aircraft as targets of the lowest
* alert level ("traffic info only"). Parked aircraft are not reported.
*
* Higher alert levels (low-level/import/urgent alert) are only triggered
* when the FlightGear TCAS instrument is installed in the aircraft model (yes,
* Flarm is not TCAS, but reusing the TCAS threat-level for the emulation
* should be good enough :-) ). The TCAS instrument classifies all AI and MP
* aircraft into 4 threat levels (from invisible to high alert) - similar to
* the actual alert levels normally provided by a Flarm device.
*
* Supported NMEA messages:
* $GPRMC: own position information
*
* Supported Garmin proprietary messages:
* $PGRMZ: own barometric altitude information in feet
*
* Supported Flarm proprietary messages:
* $PFLAU: status and intruder data
* $PFLAA: data on targets within range
* $PFLAV: version information
* $PFLAE: device status information
* $PFLAC: configuration request
* $PFLAS: debug information
*
* Module properties:
* All Flarm configuration properties are mirrored to /sim/flarm/config.
* Useful properties:
* /sim/flarm/config/RANGE Range in meters when considering traffic targets
* /sim/flarm/config/NMEAOUT NMEA message mode (0=off, 1=all, 2=Garmin/NMEA messages only, 3=Flarm messages only)
* /sim/flarm/config/ACFT Aircraft type (1=glider, 3=helicopter, 8=motor aircraft, 9=jet)
*
*/
FGFlarm::FGFlarm() :
FGGarmin(),
mFlarmMessages(FLARM::SET),
mFlarmConfig(fgGetNode("/sim/flarm/config", true)),
mLastUpdate(0)
{
// Flarm (and Garmin) devices normally report barometric altitude in feet
mMetric = false;
// disable all Garmin messages, except PGRMZ
mGarminMessages = GARMIN::PGRMZ;
// Allow processing more message lines per cycle than with FG's standard NMEA protocol,
// otherwise we won't reply quickly when a remote sends multiple configuration requests.
mMaxReceiveLines = 20;
// allow bidirectional communication (we're sending data and accepting requests)
mBiDirectionalSupport = true;
#ifdef FLARM_DEBUGGING
// show I/O debug messages
sglog().set_log_classes(SG_IO);
sglog().set_log_priority(SG_DEBUG);
#endif
// some default configuration data, to please XCSoar and other apps
const unsigned int zero=0;
setDefaultConfigValue("ACFT", 9);
setDefaultConfigValue("ADDWP", "");
setDefaultConfigValue("BAUD", 5);
setDefaultConfigValue("CFLAGS", zero);
setDefaultConfigValue("COMPID", "");
setDefaultConfigValue("COMPCLASS", "");
setDefaultConfigValue("COPIL", "");
setDefaultConfigValue("GLIDERID", "");
setDefaultConfigValue("GLIDERTYPE", "");
setDefaultConfigValue("ID", "0");
setDefaultConfigValue("LOGINT", 2);
setDefaultConfigValue("NEWTASK", "");
setDefaultConfigValue("NMEAOUT", 1); // all messages enabled
setDefaultConfigValue("NOTRACK", zero); // disabled
setDefaultConfigValue("PILOT", "Curt"); // :-)))
setDefaultConfigValue("PRIV", zero);
setDefaultConfigValue("RANGE", 25500);
setDefaultConfigValue("THRE", 2);
setDefaultConfigValue("UI", zero);
}
FGFlarm::~FGFlarm() {
}
void FGFlarm::setDefaultConfigValue(const char* ConfigKey, const char* Value)
{
if (!mFlarmConfig->hasValue(ConfigKey))
mFlarmConfig->setStringValue(ConfigKey, Value);
}
void FGFlarm::setDefaultConfigValue(const char* ConfigKey, unsigned int Value)
{
if (!mFlarmConfig->hasValue(ConfigKey))
mFlarmConfig->setIntValue(ConfigKey, Value);
}
// generate Flarm NMEA messages
bool FGFlarm::gen_message()
{
// generate generic messages first
FGGarmin::gen_message();
// traffic updates once per second only, independent of the normal protocol frequency
if ((get_count()-mLastUpdate)/get_hz() < 1.0)
{
return true;
}
mLastUpdate = get_count();
char nmea[256];
int TargetCount=0;
double ClosestDistanceM2 = 99e9;
double ClosestLond=0.0, ClosestLatd=0.0;
int ClosestRelVerticalM=0;
int ClosestThreatLevel=-1;
// obtain own position
double latd = mFdm.get_Latitude() * SGD_RADIANS_TO_DEGREES;
double lond = mFdm.get_Longitude() * SGD_RADIANS_TO_DEGREES;
// PFLAA (Flarm proprietary)
if (mFlarmMessages & FLARM::PFLAA)
{
double altitude_ft = mFdm.get_Altitude();
// check all AI/MP aircraft
SGPropertyNode* pAi = fgGetNode("/ai/models", true);
simgear::PropertyList aircraftList = pAi->getChildren("aircraft");
for (simgear::PropertyList::iterator i = aircraftList.begin(); i != aircraftList.end(); ++i)
{
SGPropertyNode* pModel = *i;
if ((pModel)&&(pModel->nChildren()))
{
double GroundSpeedKt = pModel->getDoubleValue("velocities/true-airspeed-kt", 0.0);
int threatLevel = pModel->getIntValue("tcas/threat-level", -99);
// threatLevel is undefined (-99) when no TCAS is installed
if (threatLevel == -99)
{
// set threat level to 0 (traffic info) when a/c is moving. Otherwise -1 (invisible).
threatLevel = (GroundSpeedKt>1) ? 0 : -1;
}
// report traffic, unless considered "invisible"
if (threatLevel >= 0)
{
// position data of current intruder
double targetLatd = pModel->getDoubleValue("position/latitude-deg", 0.0);
double targetLond = pModel->getDoubleValue("position/longitude-deg", 0.0);
// calculate the relative North and relative East distances in meters, as
// required by the Flarm protocol
double RelNorthAngleDeg = targetLatd - latd;
double RelNorth = ((2*SG_PI*SG_POLAR_RADIUS_M) / 360.0) * RelNorthAngleDeg;
double RelEastAngleDeg = targetLond - lond;
double RelEast = ((2*SG_PI*SG_EQUATORIAL_RADIUS_M) / 360.0) *
abs(cos(latd*SGD_DEGREES_TO_RADIANS)) * RelEastAngleDeg;
#ifdef FLARM_DEBUGGING
{
double distanceM = sqrt(RelNorth*RelNorth+RelEast*RelEast);
pModel->setDoubleValue("flarm/distance", distanceM);
pModel->setIntValue("flarm/alive", pModel->getIntValue("flarm/alive",0)+1);
}
#endif
// do not consider targets beyond 100km (1e5 meters)
double FlarmRangeM = mFlarmConfig->getIntValue("RANGE", 25500);
double DistanceM2 = RelNorth*RelNorth+RelEast*RelEast;
if (DistanceM2 < FlarmRangeM*FlarmRangeM)
{
TargetCount++;
#if 0//def FLARM_DEBUGGING
{
double distanceM = sqrt(RelNorth*RelNorth+RelEast*RelEast);
printf("%3u: id %3u, %s, distance: %.1fkm, North: %.1f, East: %.1f, speed: %.1f kt\n",
pModel->getIndex(),
pModel->getIntValue("id"),
pModel->getStringValue("callsign", "<no name>"),
distanceM/1000.0, RelNorth/1e3, RelEast/1e3, GroundSpeedKt);
}
#endif
int RelVerticalM = (pModel->getDoubleValue("position/altitude-ft", 0.0)-altitude_ft)* SG_FEET_TO_METER;
int Track = pModel->getDoubleValue("orientation/true-heading-deg", 0.0);
int ClimbRateMs = pModel->getDoubleValue("velocities/vertical-speed-fps", 0.0) * (SG_FPS_TO_KT * SG_KT_TO_MPS);
int AcftType = 9; // report as jet aircraft for now
// generate some fake 6-digit hex code
unsigned int ID = pModel->getIntValue("id") & 0x00FFFFFF;
//$PFLAA,AlarmLevel,RelNorth,RelEast,RelVertical,IDType,ID,Track,TurnRate,GroundSpeed,ClimbRate,AcftType
snprintf( nmea, 256, "$PFLAA,%u,%i,%i,%i,2,%06X,%u,,%i,%i,%u",
threatLevel, (int)RelNorth, (int)RelEast, RelVerticalM, ID,
Track, (int) (GroundSpeedKt*SG_KT_TO_MPS), ClimbRateMs, AcftType);
add_with_checksum(nmea, 256);
if (DistanceM2 < ClosestDistanceM2)
{
ClosestDistanceM2 = DistanceM2;
ClosestThreatLevel = threatLevel;
ClosestRelVerticalM = RelVerticalM;
ClosestLatd = targetLatd;
ClosestLond = targetLond;
}
}
}
}
}
}
// PFLAU (Flarm proprietary)
if (mFlarmMessages & FLARM::PFLAU)
{
if (ClosestThreatLevel < 0)
{
// no threats, but maybe some targets
snprintf( nmea, 256, "$PFLAU,%u,1,1,1,0,,0,,", TargetCount);
}
else
{
// calculate the bearing and range of the closest target
double az2, bearing, distanceM;
geo_inverse_wgs_84(latd, lond, ClosestLatd, ClosestLond, &bearing, &az2, &distanceM);
// calculate relative bearing
double heading = mFdm.get_Psi_deg();
bearing -= heading;
SG_NORMALIZE_RANGE(bearing, -180.0, 180.0);
// set alert mode, depending on TCAS threat level
int AlertMode = 0; // no alert
if (ClosestThreatLevel >= 2) // TCAS RA alert
AlertMode = 2; // alarm!
else
if (ClosestThreatLevel == 1) // TCAS proximity alert
AlertMode = 1; // warning
snprintf( nmea, 256, "$PFLAU,%u,1,1,1,%u,%.0f,%u,%u,%u",
TargetCount, ClosestThreatLevel, bearing, AlertMode, ClosestRelVerticalM, (unsigned int) distanceM);
}
add_with_checksum(nmea, 256);
}
return true;
}
// process a Flarm sentence
void FGFlarm::parse_message(const std::vector<std::string>& tokens)
{
char nmea[256];
if ( tokens[0] == "PFLAE" )
{
if (tokens.size()<2)
return;
// #1: request
const string& request = tokens[1];
SG_LOG( SG_IO, SG_DEBUG, " PFLAE request = " << request );
if (request == "R")
{
// report "no errors"
snprintf( nmea, 256, "$PFLAE,A,0,0");
add_with_checksum(nmea, 256);
SG_LOG( SG_IO, SG_DEBUG, " PFLAE reply= " << nmea );
}
}
else
if ( tokens[0] == "PFLAV" )
{
if (tokens.size()<2)
return;
// #1: request
const string& request = tokens[1];
SG_LOG( SG_IO, SG_DEBUG, " PFLAV version request = " << request );
if (request == "R")
{
// report some fixed version to please the requesting device
snprintf( nmea, 256, "$PFLAV,A,2.00,6.00,");
add_with_checksum(nmea, 256);
SG_LOG( SG_IO, SG_DEBUG, " PFLAV reply= " << nmea );
}
}
else
if ( tokens[0] == "PFLAS" )
{
// status/debug information
if (tokens.size()<2)
return;
// #1: request
const string& request = tokens[1];
SG_LOG( SG_IO, SG_DEBUG, " PFLAS status/debug request = " << request );
if (request == "R")
{
// Report some debug data to please the requesting device.
// Apparently debug replies are not in NMEA format and contain plain text.
const char* FlrmDebugReply = (
"------------------------------------------\r\n"
"FlightGear " FLIGHTGEAR_VERSION "\r\n"
"Revision " REVISION "\r\n"
"------------------------------------------\r\n");
mNmeaSentence += FlrmDebugReply;
SG_LOG( SG_IO, SG_DEBUG, " PFLAS reply = " << FlrmDebugReply );
}
}
else
if ( tokens[0] == "PFLAC" )
{
// configuration command
if (tokens.size()<3)
return;
bool Error = true;
// #1: request
const string& request = tokens[1];
SG_LOG( SG_IO, SG_DEBUG, " PFLAC config request = " << request );
// #2: keyword
const string& keyword = tokens[2];
SG_LOG( SG_IO, SG_DEBUG, " PFLAC config request = " << keyword );
// check if the config element is supported
SGPropertyNode* configNode = mFlarmConfig->getChild(keyword,0,false);
if (!configNode)
{
// unsupported configuration element
}
else
if (request == "R")
{
// reply with config data
snprintf( nmea, 256, "$PFLAC,A,%s,%s",
keyword.c_str(), configNode->getStringValue().c_str());
add_with_checksum(nmea, 256);
Error = false;
}
else
if (request == "S")
{
if (tokens.size()<4)
return;
Error = false;
// special handling for some parameters
if (keyword == "NMEAOUT")
{
// #3: value
int value = (int) atof(tokens[3].c_str());
switch(value % 10)
{
case 0:
// disable all periodic messages
mNmeaMessages = 0;
mGarminMessages = 0;
mFlarmMessages = 0;
break;
case 1:
// enable all periodic messages
mNmeaMessages = NMEA::SET;
mGarminMessages = GARMIN::PGRMZ;
mFlarmMessages = FLARM::SET;
break;
case 2:
// enable all, except periodic Flarm messages
mNmeaMessages = NMEA::SET;
mGarminMessages = GARMIN::PGRMZ;
mFlarmMessages = 0;
break;
case 3:
// enable all, except periodic NMEA messages
mNmeaMessages = 0;
mGarminMessages = GARMIN::PGRMZ;
mFlarmMessages = FLARM::SET;
break;
default:
Error = true;
}
}
if (!Error)
{
// just store the config data as it is
configNode->setStringValue(tokens[3]);
// reply
snprintf( nmea, 256, "$PFLAC,A,%s,%s",
keyword.c_str(), configNode->getStringValue().c_str());
add_with_checksum(nmea, 256);
}
}
if (Error)
{
// report error for unsupported requests
snprintf( nmea, 256, "$PFLAC,A,ERROR");
add_with_checksum(nmea, 256);
}
}
else
{
// Invalid or unsupported message.
// In flarm mode, we only accept flarm messages as input, but no other messages, i.e. no position updates.
// Use the Garmin or NMEA basic protocols to feed position data into FG.
SG_LOG( SG_IO, SG_DEBUG, " Unsupported message = " << tokens[0] );
}
}

58
src/Network/flarm.hxx Normal file
View File

@@ -0,0 +1,58 @@
// flarm.hxx -- Flarm protocol class
//
// Written by Thorsten Brehm, started November 2017.
//
// Copyright (C) 2017 Thorsten Brehm - brehmt (at) gmail 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$
#ifndef _FG_FLARM_HXX
#define _FG_FLARM_HXX
#include "garmin.hxx"
namespace NMEA
{
// Flarm proprietary messages
namespace FLARM
{
const unsigned int PFLAU = (1<<0);
const unsigned int PFLAA = (1<<1);
const unsigned int SET = (PFLAU|PFLAA);
}
}
class FGFlarm : public FGGarmin {
protected:
unsigned int mFlarmMessages;
SGPropertyNode_ptr mFlarmConfig;
unsigned long mLastUpdate;
// process a Flarm sentence
virtual void parse_message(const std::vector<std::string>& tokens);
void setDefaultConfigValue(const char* ConfigKey, const char* Value);
void setDefaultConfigValue(const char* ConfigKey, unsigned int Value);
public:
FGFlarm();
~FGFlarm();
virtual bool gen_message();
};
#endif // _FG_FLARM_HXX

103
src/Network/garmin.cxx Normal file
View File

@@ -0,0 +1,103 @@
// garmin.cxx -- Garmin protocol class
//
// Written by Curtis Olson, started November 1999.
//
// Copyright (C) 1999 Curtis L. Olson - http://www.flightgear.org/~curt
//
// 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 <simgear/debug/logstream.hxx>
#include <FDM/flightProperties.hxx>
#include <simgear/constants.h>
#include "garmin.hxx"
using namespace NMEA;
FGGarmin::FGGarmin() :
FGNMEA(),
mGarminMessages(GARMIN::PGRMZ),
mMetric(true) // use metric altitude reports
// In fact Garmin devices normally seem report barometric altitude in feet (not meters), but
// the FG implementation has always used metric reports for years (and we keep it this way,
// to avoid complaints about changed implementation). The unit is also part of the NMEA message,
// so smart devices are probably ok with both anyway.
{
// only enable the GPRMC standard NMEA message
mNmeaMessages = NMEA::GPRMC;
// Garmin uses CR-LF line feeds.
mLineFeed = "\r\n";
}
FGGarmin::~FGGarmin()
{
}
// generate Garmin NMEA messages
bool FGGarmin::gen_message()
{
(void) FGNMEA::gen_message();
char nmea[256];
// RMZ sentence (Garmin proprietary)
if (mGarminMessages & GARMIN::PGRMZ)
{
double altitude_ft = mFdm.get_Altitude();
// $PGRMZ,AAAA.A,F,T*XX
if (mMetric)
sprintf( nmea, "$PGRMZ,%.1f,M,3", altitude_ft * SG_FEET_TO_METER );
else
sprintf( nmea, "$PGRMZ,%.1f,F,3", altitude_ft );
add_with_checksum(nmea, 256);
}
return true;
}
// process a Garmin sentence
void FGGarmin::parse_message(const std::vector<std::string>& tokens)
{
if (tokens[0] == "PGRMZ")
{
if (tokens.size()<3)
return;
// #1: altitude
double altitude = atof( tokens[1].c_str() );
// #2: altitude units
const std::string& alt_units = tokens[2];
if ( alt_units != "F" && alt_units != "f" )
altitude *= SG_METER_TO_FEET;
mFdm.set_Altitude( altitude );
SG_LOG( SG_IO, SG_DEBUG, " altitude = " << altitude );
}
else
{
// not a Garmin message. Maybe standard NMEA message.
FGNMEA::parse_message(tokens);
}
}

55
src/Network/garmin.hxx Normal file
View File

@@ -0,0 +1,55 @@
// garmin.hxx -- Garmin protocol class
//
// Written by Curtis Olson, started November 1999.
//
// Copyright (C) 1999 Curtis L. Olson - http://www.flightgear.org/~curt
//
// 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$
#ifndef _FG_GARMIN_HXX
#define _FG_GARMIN_HXX
#include "nmea.hxx"
namespace NMEA
{
// Garmin proprietary messages
namespace GARMIN
{
const unsigned int PGRMZ = (1<<0);
}
}
class FGGarmin : public FGNMEA {
protected:
unsigned int mGarminMessages;
bool mMetric;
// process a Garmin sentence
virtual void parse_message(const std::vector<std::string>& tokens);
public:
FGGarmin();
~FGGarmin();
virtual bool gen_message();
};
#endif // _FG_GARMIN_HXX

1009
src/Network/generic.cxx Normal file

File diff suppressed because it is too large Load Diff

124
src/Network/generic.hxx Normal file
View File

@@ -0,0 +1,124 @@
// generic.hxx -- generic protocol class
//
// Written by Curtis Olson, started November 1999.
//
// Copyright (C) 1999 Curtis L. Olson - http://www.flightgear.org/~curt
//
// 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$
#pragma once
#include <simgear/compiler.h>
#include <string>
#include "protocol.hxx"
class FGGeneric : public FGProtocol {
public:
FGGeneric(vector<std::string>);
~FGGeneric();
bool gen_message();
bool parse_message_len(int length);
// open hailing frequencies
bool open();
void reinit();
// process work for this port
bool process();
// close the channel
bool close();
void setExitOnError(bool val) { exitOnError = val; }
bool getExitOnError() { return exitOnError; }
bool getInitOk(void) { return initOk; }
protected:
enum e_type { FG_BOOL=0, FG_INT, FG_FLOAT, FG_DOUBLE, FG_STRING, FG_FIXED, FG_BYTE, FG_WORD };
typedef struct {
// std::string name;
std::string format;
e_type type;
double offset;
double factor;
double min, max;
bool wrap;
bool rel;
SGPropertyNode_ptr prop;
} _serial_prot;
private:
std::string file_name;
int length;
char buf[ FG_MAX_MSG_SIZE ];
std::string preamble;
std::string postamble;
std::string var_separator;
std::string line_separator;
std::string var_sep_string;
std::string line_sep_string;
vector<_serial_prot> _out_message;
vector<_serial_prot> _in_message;
bool binary_mode;
enum {FOOTER_NONE, FOOTER_LENGTH, FOOTER_MAGIC} binary_footer_type;
int binary_footer_value;
int binary_record_length;
enum {BYTE_ORDER_NEEDS_CONVERSION, BYTE_ORDER_MATCHES_NETWORK_ORDER} binary_byte_order;
bool gen_message_ascii();
bool gen_message_binary();
bool parse_message_ascii(int length);
bool parse_message_binary(int length);
bool read_config(SGPropertyNode *root, vector<_serial_prot> &msg);
bool exitOnError;
bool initOk;
class FGProtocolWrapper * wrapper;
template<class T>
static void updateValue(_serial_prot& prot, const T& val)
{
T new_val = (prot.rel ? getValue<T>(prot.prop) : 0)
+ prot.offset
+ prot.factor * val;
if( prot.max > prot.min )
{
if( prot.wrap )
new_val = SGMisc<double>::normalizePeriodic(prot.min, prot.max, new_val);
else
new_val = SGMisc<T>::clip(new_val, prot.min, prot.max);
}
setValue(prot.prop, new_val);
}
// Special handling for bool (relative change = toggle, no min/max, no wrap)
static void updateValue(_serial_prot& prot, bool val);
};

View File

@@ -0,0 +1,38 @@
include(FlightGearComponent)
set(SOURCES
httpd.cxx
ScreenshotUriHandler.cxx
PropertyUriHandler.cxx
JsonUriHandler.cxx
FlightHistoryUriHandler.cxx
PkgUriHandler.cxx
RunUriHandler.cxx
MirrorPropertyTreeWebsocket.cxx
NavdbUriHandler.cxx
PropertyChangeWebsocket.cxx
PropertyChangeObserver.cxx
jsonprops.cxx
SimpleDOM.cxx
)
set(HEADERS
urihandler.hxx
httpd.hxx
ScreenshotUriHandler.hxx
PropertyUriHandler.hxx
JsonUriHandler.hxx
FlightHistoryUriHandler.hxx
PkgUriHandler.hxx
RunUriHandler.hxx
NavdbUriHandler.hxx
HTTPRequest.hxx
Websocket.hxx
PropertyChangeWebsocket.hxx
PropertyChangeObserver.hxx
MirrorPropertyTreeWebsocket.hxx
jsonprops.hxx
SimpleDOM.hxx
)
flightgear_component(Http "${SOURCES}" "${HEADERS}")

View File

@@ -0,0 +1,314 @@
// FlightHistoryUriHandler.cxx -- FlightHistory service
//
// Written by Torsten Dreyer, started February 2015.
//
// Copyright (C) 2015 Torsten Dreyer
//
// 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.
#include "FlightHistoryUriHandler.hxx"
#include "SimpleDOM.hxx"
#include <cJSON.h>
#include <Aircraft/FlightHistory.hxx>
#include <Main/fg_props.hxx>
#include <sstream>
using std::string;
using std::stringstream;
namespace flightgear {
namespace http {
/*
{
type: "Feature",
geometry: {
type: "LineString",
coordinates: [lon,lat,alt],..]
},
properties: {
type: "FlightHistory"
},
}
*/
static const char * errorPage =
"<html><head><title>Flight History</title></head><body>"
"<h1>Flight History</h1>"
"Supported formats:"
"<ul>"
"<li><a href='track.json'>JSON</a></li>"
"<li><a href='track.kml'>KML</a></li>"
"</ul>"
"</body></html>";
static string FlightHistoryToJson(const SGGeodVec & history, size_t last_seen ) {
cJSON * feature = cJSON_CreateObject();
cJSON_AddItemToObject(feature, "type", cJSON_CreateString("Feature"));
cJSON * lineString = cJSON_CreateObject();
cJSON_AddItemToObject(feature, "geometry", lineString );
cJSON * properties = cJSON_CreateObject();
cJSON_AddItemToObject(feature, "properties", properties );
cJSON_AddItemToObject(properties, "type", cJSON_CreateString("FlightHistory"));
cJSON_AddItemToObject(properties, "last", cJSON_CreateNumber(last_seen));
cJSON_AddItemToObject(lineString, "type", cJSON_CreateString("LineString"));
cJSON * coordinates = cJSON_CreateArray();
cJSON_AddItemToObject(lineString, "coordinates", coordinates);
for (SGGeodVec::const_iterator it = history.begin(); it != history.end();
++it) {
cJSON * coordinate = cJSON_CreateArray();
cJSON_AddItemToArray(coordinates, coordinate);
cJSON_AddItemToArray(coordinate, cJSON_CreateNumber(it->getLongitudeDeg()));
cJSON_AddItemToArray(coordinate, cJSON_CreateNumber(it->getLatitudeDeg()));
cJSON_AddItemToArray(coordinate, cJSON_CreateNumber(it->getElevationM()));
}
char * jsonString = cJSON_PrintUnformatted(feature);
string reply(jsonString);
free(jsonString);
cJSON_Delete(lineString);
return reply;
}
static string AutoUpdateResponse(const HTTPRequest & request,
const string & base, const string & interval) {
string url = "http://";
url.append(request.HeaderVariables.get("Host")).append(base);
std::string reply("<?xml version='1.0' encoding='UTF-8'?>");
DOMNode * kml = new DOMNode("kml");
kml->setAttribute("xmlns", "http://www.opengis.net/kml/2.2");
DOMNode * document = kml->addChild(new DOMNode("Document"));
DOMNode * networkLink = document->addChild(new DOMNode("NetworkLink"));
DOMNode * link = networkLink->addChild(new DOMNode("Link"));
link->addChild(new DOMNode("href"))->addChild(new DOMTextElement(url));
link->addChild(new DOMNode("refreshMode"))->addChild(
new DOMTextElement("onInterval"));
link->addChild(new DOMNode("refreshInterval"))->addChild(
new DOMTextElement(interval.empty() ? "10" : interval));
reply.append(kml->render());
delete kml;
return reply;
}
static string FlightHistoryToKml(const SGGeodVec & history,
const HTTPRequest & request) {
string interval = request.RequestVariables.get("interval");
if (!interval.empty()) {
return AutoUpdateResponse(request, "/flighthistory/track.kml", interval);
}
std::string reply("<?xml version='1.0' encoding='UTF-8'?>");
DOMNode * kml = new DOMNode("kml");
kml->setAttribute("xmlns", "http://www.opengis.net/kml/2.2");
DOMNode * document = kml->addChild(new DOMNode("Document"));
document->addChild(new DOMNode("name"))->addChild(
new DOMTextElement("FlightGear"));
document->addChild(new DOMNode("description"))->addChild(
new DOMTextElement("FlightGear Flight History"));
DOMNode * style = document->addChild(new DOMNode("Style"))->setAttribute(
"id", "flight-history");
DOMNode * lineStyle = style->addChild(new DOMNode("LineStyle"));
string lineColor = request.RequestVariables.get("LineColor");
string lineWidth = request.RequestVariables.get("LineWidth");
string polyColor = request.RequestVariables.get("PolyColor");
lineStyle->addChild(new DOMNode("color"))->addChild(
new DOMTextElement(lineColor.empty() ? "427ebfff" : lineColor));
lineStyle->addChild(new DOMNode("width"))->addChild(
new DOMTextElement(lineWidth.empty() ? "4" : lineWidth));
lineStyle = style->addChild(new DOMNode("PolyStyle"));
lineStyle->addChild(new DOMNode("color"))->addChild(
new DOMTextElement(polyColor.empty() ? "fbfc4600" : polyColor));
DOMNode * placemark = document->addChild(new DOMNode("Placemark"));
placemark->addChild(new DOMNode("name"))->addChild(
new DOMTextElement("Flight Path"));
placemark->addChild(new DOMNode("styleUrl"))->addChild(
new DOMTextElement("#flight-history"));
DOMNode * linestring = placemark->addChild(new DOMNode("LineString"));
linestring->addChild(new DOMNode("extrude"))->addChild(
new DOMTextElement("1"));
linestring->addChild(new DOMNode("tessalate"))->addChild(
new DOMTextElement("1"));
linestring->addChild(new DOMNode("altitudeMode"))->addChild(
new DOMTextElement("absolute"));
stringstream ss;
for (SGGeodVec::const_iterator it = history.begin(); it != history.end();
++it) {
ss << (*it).getLongitudeDeg() << "," << (*it).getLatitudeDeg() << ","
<< it->getElevationM() << " ";
}
linestring->addChild(new DOMNode("coordinates"))->addChild(
new DOMTextElement(ss.str()));
reply.append(kml->render());
delete kml;
return reply;
}
static bool GetJsonDouble(cJSON * json, const char * item, double & out) {
cJSON * cj = cJSON_GetObjectItem(json, item);
if (NULL == cj)
return false;
if (cj->type != cJSON_Number)
return false;
out = cj->valuedouble;
return true;
}
static bool GetJsonBool(cJSON * json, const char * item, bool & out) {
cJSON * cj = cJSON_GetObjectItem(json, item);
if (NULL == cj)
return false;
if (cj->type == cJSON_True) {
out = true;
return true;
}
if (cj->type == cJSON_False) {
out = true;
return true;
}
return false;
}
bool FlightHistoryUriHandler::handleRequest(const HTTPRequest & request,
HTTPResponse & response, Connection * connection) {
response.Header["Access-Control-Allow-Origin"] = "*";
response.Header["Access-Control-Allow-Methods"] = "OPTIONS, GET, POST";
response.Header["Access-Control-Allow-Headers"] =
"Origin, Accept, Content-Type, X-Requested-With, X-CSRF-Token";
if (request.Method == "OPTIONS") {
return true; // OPTIONS only needs the headers
}
FGFlightHistory* history =
static_cast<FGFlightHistory*>(globals->get_subsystem("history"));
double minEdgeLengthM = 50;
string requestPath = request.Uri.substr(getUri().length());
if (request.Method == "GET") {
} else if (request.Method == "POST") {
/*
* {
* sampleIntervalSec: (number),
* maxMemoryUseBytes: (number),
* clearOnTakeoff: (bool),
* enabled: (bool),
* }
*/
cJSON * json = cJSON_Parse(request.Content.c_str());
if ( NULL != json) {
double d = .0;
bool b = false;
bool doReinit = false;
if (GetJsonDouble(json, "sampleIntervalSec", d)) {
fgSetDouble("/sim/history/sample-interval-sec", d);
doReinit = true;
}
if (GetJsonDouble(json, "maxMemoryUseBytes", d)) {
fgSetDouble("/sim/history/max-memory-use-bytes", d);
doReinit = true;
}
if (GetJsonBool(json, "clearOnTakeoff", b)) {
fgSetBool("/sim/history/clear-on-takeoff", b);
doReinit = true;
}
if (GetJsonBool(json, "enabled", b)) {
fgSetBool("/sim/history/enabled", b);
}
if (doReinit) {
history->reinit();
}
cJSON_Delete(json);
}
response.Content = "{}";
return true;
} else {
SG_LOG(SG_NETWORK, SG_INFO,
"PkgUriHandler: invalid request method '" << request.Method << "'");
response.Header["Allow"] = "OPTIONS, GET, POST";
response.StatusCode = 405;
response.Content = "{}";
return true;
}
if (requestPath == "track.kml") {
response.Header["Content-Type"] =
"application/vnd.google-earth.kml+xml; charset=UTF-8";
response.Content = FlightHistoryToKml(
history->pathForHistory(minEdgeLengthM), request);
} else if (requestPath == "track.json") {
size_t count = -1;
try {
count = std::stoul(request.RequestVariables.get("count"));
}
catch( ... ) {
}
size_t last = 0;
try {
last = std::stoul(request.RequestVariables.get("last"));
}
catch( ... ) {
}
response.Header["Content-Type"] = "application/json; charset=UTF-8";
PagedPathForHistory_ptr h = history->pagedPathForHistory( count, last );
response.Content = FlightHistoryToJson( h->path, h->last_seen );
} else {
response.Header["Content-Type"] = "text/html";
response.Content = errorPage;
}
return true;
}
} // namespace http
} // namespace flightgear

View File

@@ -0,0 +1,40 @@
// FlightHistoryUriHandler.hxx -- FlightHistory service
//
// Written by Torsten Dreyer, started February 2015.
//
// Copyright (C) 2015 Torsten Dreyer
//
// 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.
#ifndef __FG_FLIGHTHISTORY_URI_HANDLER_HXX
#define __FG_FLIGHTHISTORY_URI_HANDLER_HXX
#include "urihandler.hxx"
#include <simgear/props/props.hxx>
namespace flightgear {
namespace http {
class FlightHistoryUriHandler : public URIHandler {
public:
FlightHistoryUriHandler( const std::string& uri = "/flighthistory/" ) : URIHandler( uri ) {}
virtual bool handleRequest( const HTTPRequest & request, HTTPResponse & response, Connection * connection );
private:
};
} // namespace http
} // namespace flightgear
#endif //#define __FG_FLIGHTHISTORY_URI_HANDLER_HXX

View File

@@ -0,0 +1,65 @@
// HTTPRequest.hxx -- Wraps a http Request
//
// Written by Torsten Dreyer, started April 2014.
//
// Copyright (C) 2014 Torsten Dreyer
//
// 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.
#ifndef FG_HTTPREQUEST_HXX
#define FG_HTTPREQUEST_HXX
#include <string>
#include <map>
namespace flightgear {
namespace http {
class HTTPRequest
{
public:
HTTPRequest() {}
virtual ~HTTPRequest() {}
std::string Method;
std::string Uri;
std::string HttpVersion;
std::string QueryString;
std::string remoteAddress;
int remotePort;
std::string localAddress;
int localPort;
std::string Content;
class StringMap : public std::map<std::string,std::string> {
public:
std::string get( const std::string & key ) const {
const_iterator it = find( key );
return it == end() ? "" : it->second;
}
};
StringMap RequestVariables;
StringMap HeaderVariables;
};
}
} // namespace flightgear
#endif // FG_HTTPREQUEST_HXX

View File

@@ -0,0 +1,51 @@
// HTTPResponse.hxx -- Wraps a http Response
//
// Written by Torsten Dreyer, started April 2014.
//
// Copyright (C) 2014 Torsten Dreyer
//
// 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.
#ifndef FG_HTTPRESPONSE_HXX
#define FG_HTTPRESPONSE_HXX
#include <string>
#include <map>
namespace flightgear {
namespace http {
class HTTPResponse
{
public:
HTTPResponse();
virtual ~HTTPResponse() {}
int StatusCode;
std::string Content;
typedef std::map<std::string,std::string> Header_t;
Header_t Header;
};
inline HTTPResponse::HTTPResponse() :
StatusCode(200)
{
}
}
} // namespace flightgear
#endif // FG_HTTPRESPONSE_HXX

View File

@@ -0,0 +1,111 @@
// JsonUriHandler.cxx -- json interface to the property tree
//
// Written by Torsten Dreyer, started April 2014.
//
// Copyright (C) 2014 Torsten Dreyer
//
// 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.
#include "JsonUriHandler.hxx"
#include "jsonprops.hxx"
#include <Main/fg_props.hxx>
using std::string;
namespace flightgear {
namespace http {
bool JsonUriHandler::handleRequest( const HTTPRequest & request, HTTPResponse & response, Connection * connection )
{
response.Header["Content-Type"] = "application/json; charset=UTF-8";
response.Header["Access-Control-Allow-Origin"] = "*";
response.Header["Access-Control-Allow-Methods"] = "OPTIONS, GET, POST";
response.Header["Access-Control-Allow-Headers"] = "Origin, Accept, Content-Type, X-Requested-With, X-CSRF-Token";
if( request.Method == "OPTIONS" ){
return true; // OPTIONS only needs the headers
}
if( request.Method == "GET" ){
// max recursion depth
int depth = atoi(request.RequestVariables.get("d").c_str());
if( depth < 1 ) depth = 1; // at least one level
// pretty print (y) or compact print (default)
bool indent = request.RequestVariables.get("i") == "y";
bool timestamp = request.RequestVariables.get("t") == "y";
SGPropertyNode_ptr node = getRequestedNode(request );
if( !node.valid() ) {
response.StatusCode = 404;
response.Content = "{}";
return true;
}
response.Content = JSON::toJsonString( indent, node, depth, timestamp ? fgGetDouble("/sim/time/elapsed-sec") : -1.0 );
return true;
}
if( request.Method == "POST" ) {
SGPropertyNode_ptr node = getRequestedNode(request );
if( !node.valid() ) {
response.StatusCode = 404;
response.Content = "{}";
return true;
}
SG_LOG(SG_NETWORK,SG_INFO, "JsonUriHandler: setting property from'" << request.Content << "'" );
cJSON * json = cJSON_Parse( request.Content.c_str() );
if( NULL != json ) {
JSON::toProp( json, node );
cJSON_Delete(json);
}
response.Content = "{}";
return true;
}
SG_LOG(SG_NETWORK,SG_INFO, "JsonUriHandler: invalid request method '" << request.Method << "'" );
response.Header["Allow"] = "OPTIONS, GET, POST";
response.StatusCode = 405;
response.Content = "{}";
return true;
}
SGPropertyNode_ptr JsonUriHandler::getRequestedNode(const HTTPRequest & request)
{
SG_LOG(SG_NETWORK,SG_INFO, "JsonUriHandler: request is '" << request.Uri << "'" );
string propertyPath = request.Uri;
propertyPath = propertyPath.substr( getUri().size() );
// skip trailing '/' - not very efficient but shouldn't happen too often
while( !propertyPath.empty() && propertyPath[ propertyPath.length()-1 ] == '/' )
propertyPath = propertyPath.substr(0,propertyPath.length()-1);
SGPropertyNode_ptr reply = fgGetNode( string("/") + propertyPath );
if( !reply.valid() ) {
SG_LOG(SG_NETWORK,SG_WARN, "JsonUriHandler: requested node not found: '" << propertyPath << "'");
}
return reply;
}
} // namespace http
} // namespace flightgear

View File

@@ -0,0 +1,41 @@
// JsonUriHandler.hxx -- json interface to the property tree
//
// Written by Torsten Dreyer, started April 2014.
//
// Copyright (C) 2014 Torsten Dreyer
//
// 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.
#ifndef __FG_JSON_URI_HANDLER_HXX
#define __FG_JSON_URI_HANDLER_HXX
#include "urihandler.hxx"
#include <simgear/props/props.hxx>
namespace flightgear {
namespace http {
class JsonUriHandler : public URIHandler {
public:
JsonUriHandler( const std::string& uri = "/json/" ) : URIHandler( uri ) {}
virtual bool handleRequest( const HTTPRequest & request, HTTPResponse & response, Connection * connection );
private:
SGPropertyNode_ptr getRequestedNode(const HTTPRequest & request);
};
} // namespace http
} // namespace flightgear
#endif //#define __FG_SCREENSHOT_URI_HANDLER_HXX

View File

@@ -0,0 +1,542 @@
// MirrorPropertyTreeWebsocket.cxx -- A websocket for mirroring a property sub-tree
//
// Written by James Turner, started November 2016.
//
// Copyright (C) 2016 James Turner
//
// 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.
#include "MirrorPropertyTreeWebsocket.hxx"
#include "jsonprops.hxx"
#include <algorithm>
#include <unordered_map>
#include <set>
#include <simgear/debug/logstream.hxx>
#include <simgear/props/props.hxx>
#include <simgear/structure/commands.hxx>
#include <simgear/props/props_io.hxx>
#include <Main/globals.hxx>
#include <Main/fg_props.hxx>
#include <cJSON.h>
//#define MIRROR_DEBUG 1
namespace flightgear {
namespace http {
using std::string;
typedef unsigned int PropertyId; // connection local property id
struct PropertyValue
{
PropertyValue(SGPropertyNode* cur = nullptr) :
type(simgear::props::NONE)
{
if (!cur) {
return;
}
type = cur->getType();
switch (type) {
case simgear::props::INT:
intValue = cur->getIntValue();
break;
case simgear::props::BOOL:
intValue = cur->getBoolValue();
break;
case simgear::props::FLOAT:
case simgear::props::DOUBLE:
doubleValue = cur->getDoubleValue();
break;
case simgear::props::STRING:
case simgear::props::UNSPECIFIED:
stringValue = cur->getStringValue();
break;
case simgear::props::NONE:
break;
default:
SG_LOG(SG_NETWORK, SG_DEV_ALERT, "MirrorPropTree PropertyValue : implement me!" << type);
break;
}
}
bool equals(SGPropertyNode* node, const PropertyValue& other) const
{
if (other.type != type) return false;
switch (type) {
case simgear::props::INT:
case simgear::props::BOOL:
return intValue == other.intValue;
case simgear::props::FLOAT:
case simgear::props::DOUBLE:
return std::fabs(doubleValue - other.doubleValue) < 1e-4;
case simgear::props::STRING:
case simgear::props::UNSPECIFIED:
return stringValue == other.stringValue;
case simgear::props::NONE:
return true;
default:
break;
}
return false;
}
simgear::props::Type type;
union {
int intValue;
double doubleValue;
};
std::string stringValue;
};
struct RemovedNode
{
RemovedNode(SGPropertyNode* node, unsigned int aId) :
path(node->getPath()),
id(aId)
{}
std::string path;
unsigned int id = 0;
bool operator==(const RemovedNode& other) const
{
return (path == other.path);
}
};
class MirrorTreeListener : public SGPropertyChangeListener
{
public:
MirrorTreeListener() : SGPropertyChangeListener(true /* recursive */)
{
previousValues.resize(2);
}
virtual ~MirrorTreeListener()
{
}
void valueChanged(SGPropertyNode* node) override
{
auto it = idHash.find(node);
if (it == idHash.end()) {
// not new to the server, but new to the client
newNodes.insert(node);
} else {
assert(previousValues.size() > it->second);
PropertyValue newVal(node);
if (!previousValues[it->second].equals(node, newVal)) {
previousValues[it->second] = newVal;
changedNodes.insert(node);
}
}
}
void childAdded(SGPropertyNode* parent, SGPropertyNode* child) override
{
SG_UNUSED(parent);
recursiveAdd(child);
}
void recursiveAdd(SGPropertyNode* node)
{
RemovedNode r(node, 0 /* id not actually used */);
#if defined (MIRROR_DEBUG)
SG_LOG(SG_NETWORK, SG_INFO, "looking for RR:" << r.path);
#endif
auto rrIt = std::find(recentlyRemoved.begin(), recentlyRemoved.end(), r);
if (rrIt != recentlyRemoved.end()) {
#if defined (MIRROR_DEBUG)
SG_LOG(SG_NETWORK, SG_INFO, "recycling node:" << node->getPath());
#endif
const auto id = rrIt->id;
// recycle nodes which get thrashed from Nasal (deleted + re-created
// each time a Nasal timer fires)
removedNodes.erase(id); // don't remove it!
idHash.insert(std::make_pair(node, id));
// we can still do change compression here, but this also
// deals with type mutation when removing + re-adding with a
// different type
PropertyValue newVal(node);
if (!previousValues[id].equals(node, newVal)) {
previousValues[id] = newVal;
changedNodes.insert(node);
#if defined (MIRROR_DEBUG)
SG_LOG(SG_NETWORK, SG_INFO, "\tand will actually change" << node->getPath());
#endif
}
recentlyRemoved.erase(rrIt);
return;
}
#if defined (MIRROR_DEBUG)
SG_LOG(SG_NETWORK, SG_INFO, "new node:" << node->getPath());
#endif
newNodes.insert(node);
int child = 0;
for (; child < node->nChildren(); ++child) {
recursiveAdd(node->getChild(child));
}
}
void childRemoved(SGPropertyNode* parent, SGPropertyNode* child) override
{
changedNodes.erase(child); // have to do this here with the pointer valid
newNodes.erase(child);
auto it = idHash.find(child);
if (it != idHash.end()) {
removedNodes.insert(it->second);
idHash.erase(it);
// record so we can map removed+add of the same property into
// a simple value change (this happens commonly with the canvas
// due to lazy Nasal scripting)
recentlyRemoved.emplace_back(child, it->second);
#if defined (MIRROR_DEBUG)
SG_LOG(SG_NETWORK, SG_INFO, "adding RR:" << recentlyRemoved.back().path);
#endif
}
#if defined (MIRROR_DEBUG)
SG_LOG(SG_NETWORK, SG_INFO, "saw remove of:" << child->getPath());
#endif
}
void registerSubtree(SGPropertyNode* node)
{
#if defined (MIRROR_DEBUG)
SG_LOG(SG_NETWORK, SG_INFO, "register subtree:" << node->getPath());
#endif
valueChanged(node);
// and recurse
int child = 0;
for (; child < node->nChildren(); ++child) {
registerSubtree(node->getChild(child));
}
}
std::set<SGPropertyNode*> newNodes;
std::set<SGPropertyNode*> changedNodes;
std::set<PropertyId> removedNodes;
PropertyId idForProperty(SGPropertyNode* prop)
{
auto it = idHash.find(prop);
if (it == idHash.end()) {
it = idHash.insert(it, std::make_pair(prop, nextPropertyId++));
previousValues.push_back(PropertyValue(prop));
}
return it->second;
}
cJSON* makeJSONData()
{
#if defined (MIRROR_DEBUG)
SGTimeStamp st;
st.stamp();
int newSize = newNodes.size();
int changedSize = changedNodes.size();
int removedSize = removedNodes.size();
#endif
cJSON* result = cJSON_CreateObject();
if (!newNodes.empty()) {
cJSON * newNodesArray = cJSON_CreateArray();
// cJSON_AddItemToArray performance is O(N) due to use of a linked
// list, which dominates the performance here. To fix this we maintan
// a point to the tail of the array, keeping appends O(1)
cJSON* arrayTail = nullptr;
for (auto prop : newNodes) {
changedNodes.erase(prop); // avoid duplicate send
cJSON* newPropData = cJSON_CreateObject();
cJSON_AddItemToObject(newPropData, "path", cJSON_CreateString(prop->getPath(true).c_str()));
cJSON_AddItemToObject(newPropData, "type", cJSON_CreateString(JSON::getPropertyTypeString(prop->getType())));
cJSON_AddItemToObject(newPropData, "index", cJSON_CreateNumber(prop->getIndex()));
cJSON_AddItemToObject(newPropData, "position", cJSON_CreateNumber(prop->getPosition()));
cJSON_AddItemToObject(newPropData, "id", cJSON_CreateNumber(idForProperty(prop)));
if (prop->getType() != simgear::props::NONE) {
cJSON_AddItemToObject(newPropData, "value", JSON::valueToJson(prop));
}
if (arrayTail) {
arrayTail->next = newPropData;
newPropData->prev = arrayTail;
arrayTail = newPropData;
} else {
cJSON_AddItemToArray(newNodesArray, newPropData);
arrayTail = newPropData;
}
}
newNodes.clear();
cJSON_AddItemToObject(result, "created", newNodesArray);
}
if (!removedNodes.empty()) {
cJSON * deletedNodesArray = cJSON_CreateArray();
for (auto propId : removedNodes) {
cJSON_AddItemToArray(deletedNodesArray, cJSON_CreateNumber(propId));
}
cJSON_AddItemToObject(result, "removed", deletedNodesArray);
removedNodes.clear();
}
if (!changedNodes.empty()) {
cJSON * changedNodesArray = cJSON_CreateArray();
// see comment above about cJSON_AddItemToArray
cJSON* tail = nullptr;
for (auto prop : changedNodes) {
cJSON* propData = cJSON_CreateArray();
cJSON_AddItemToArray(propData, cJSON_CreateNumber(idForProperty(prop)));
cJSON_AddItemToArray(propData, JSON::valueToJson(prop));
if (tail) {
tail->next = propData;
propData->prev = tail;
tail = propData;
} else {
cJSON_AddItemToArray(changedNodesArray, propData);
tail = propData;
}
}
changedNodes.clear();
cJSON_AddItemToObject(result, "changed", changedNodesArray);
}
#if defined (MIRROR_DEBUG)
SG_LOG(SG_NETWORK, SG_INFO, "making JSON data took:" << st.elapsedMSec() << " for " << newSize << "/" << changedSize << "/" << removedSize);
#endif
recentlyRemoved.clear();
return result;
}
bool haveChangesToSend() const
{
return !newNodes.empty() || !changedNodes.empty() || !removedNodes.empty();
}
private:
PropertyId nextPropertyId = 1;
std::unordered_map<SGPropertyNode*, PropertyId> idHash;
std::vector<PropertyValue> previousValues;
/// track recently removed nodes in case they are re-created imemdiately
/// after with the same type, since we can make this much more efficient
/// when sending over the wire.
std::vector<RemovedNode> recentlyRemoved;
};
#if 0
static void handleSetCommand(const string_list& nodes, cJSON* json, WebsocketWriter &writer)
{
cJSON * value = cJSON_GetObjectItem(json, "value");
if ( NULL != value ) {
if (nodes.size() > 1) {
SG_LOG(SG_NETWORK, SG_WARN, "httpd: WS set: insufficent values for nodes:" << nodes.size());
return;
}
SGPropertyNode_ptr n = fgGetNode(nodes.front());
if (!n) {
SG_LOG(SG_NETWORK, SG_WARN, "httpd: set '" << nodes.front() << "' not found");
return;
}
setPropertyFromJson(n, value);
return;
}
cJSON * values = cJSON_GetObjectItem(json, "values");
if ( ( NULL == values ) || ( static_cast<size_t>(cJSON_GetArraySize(values)) != nodes.size()) ) {
SG_LOG(SG_NETWORK, SG_WARN, "httpd: WS set: mismatched nodes/values sizes:" << nodes.size());
return;
}
string_list::const_iterator it;
int i=0;
for (it = nodes.begin(); it != nodes.end(); ++it, ++i) {
SGPropertyNode_ptr n = fgGetNode(*it);
if (!n) {
SG_LOG(SG_NETWORK, SG_WARN, "httpd: get '" << *it << "' not found");
return;
}
setPropertyFromJson(n, cJSON_GetArrayItem(values, i));
} // of nodes iteration
}
static void handleExecCommand(cJSON* json)
{
cJSON* name = cJSON_GetObjectItem(json, "fgcommand");
if ((NULL == name )|| (NULL == name->valuestring)) {
SG_LOG(SG_NETWORK, SG_WARN, "httpd: exec: no fgcommand name");
return;
}
SGPropertyNode_ptr arg(new SGPropertyNode);
JSON::addChildrenToProp( json, arg );
globals->get_commands()->execute(name->valuestring, arg);
}
#endif
MirrorPropertyTreeWebsocket::MirrorPropertyTreeWebsocket(const std::string& path) :
_rootPath(path),
_listener(new MirrorTreeListener),
_minSendInterval(100)
{
checkNodeExists();
}
MirrorPropertyTreeWebsocket::~MirrorPropertyTreeWebsocket()
{
}
void MirrorPropertyTreeWebsocket::close()
{
if (_subtreeRoot) {
_subtreeRoot->removeChangeListener(_listener.get());
}
}
void MirrorPropertyTreeWebsocket::checkNodeExists()
{
_subtreeRoot = globals->get_props()->getNode(_rootPath, false);
if (_subtreeRoot) {
_subtreeRoot->addChangeListener(_listener.get());
_listener->registerSubtree(_subtreeRoot);
_lastSendTime = SGTimeStamp::now();
}
}
void MirrorPropertyTreeWebsocket::handleRequest(const HTTPRequest & request, WebsocketWriter &writer)
{
if (!_subtreeRoot) {
checkNodeExists();
if (!_subtreeRoot) {
return; // still no node exists, we can't process this
}
}
if (request.Content.empty()) return;
#if 0
/*
* allowed JSON is
{
command : 'addListener',
nodes : [
'/bar/baz',
'/foo/bar'
],
node: '/bax/foo'
}
*/
cJSON * json = cJSON_Parse(request.Content.c_str());
if ( NULL != json) {
string command;
cJSON * j = cJSON_GetObjectItem(json, "command");
if ( NULL != j && NULL != j->valuestring) {
command = j->valuestring;
}
// handle a single node name, or an array of them
string_list nodeNames;
j = cJSON_GetObjectItem(json, "node");
if ( NULL != j && NULL != j->valuestring) {
nodeNames.push_back(simgear::strutils::strip(string(j->valuestring)));
}
cJSON * nodes = cJSON_GetObjectItem(json, "nodes");
if ( NULL != nodes) {
for (int i = 0; i < cJSON_GetArraySize(nodes); i++) {
cJSON * node = cJSON_GetArrayItem(nodes, i);
if ( NULL == node) continue;
if ( NULL == node->valuestring) continue;
nodeNames.push_back(simgear::strutils::strip(string(node->valuestring)));
}
}
if (command == "get") {
handleGetCommand(nodeNames, writer);
} else if (command == "set") {
handleSetCommand(nodeNames, json, writer);
} else if (command == "exec") {
handleExecCommand(json);
} else {
string_list::const_iterator it;
for (it = nodeNames.begin(); it != nodeNames.end(); ++it) {
_watchedNodes.handleCommand(command, *it, _propertyChangeObserver);
}
}
cJSON_Delete(json);
}
#endif
}
void MirrorPropertyTreeWebsocket::poll(WebsocketWriter & writer)
{
if (!_subtreeRoot) {
checkNodeExists();
if (!_subtreeRoot) {
return;
}
}
if (!_listener->haveChangesToSend()) {
return;
}
if (_lastSendTime.elapsedMSec() < _minSendInterval) {
return;
}
// okay, we will send now, update the send stamp
_lastSendTime.stamp();
cJSON * json = _listener->makeJSONData();
char * jsonString = cJSON_PrintUnformatted( json );
writer.writeText( jsonString );
free( jsonString );
cJSON_Delete( json );
}
} // namespace http
} // namespace flightgear

View File

@@ -0,0 +1,62 @@
// MirrorPropertyTreeWebsocket.hxx -- A websocket for mirroring a property sub-tree
//
// Written by James Turner, started November 2016.
//
// Copyright (C) 2016 James Turner
//
// 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.
#ifndef MIRROR_PROP_TREE_WEBSOCKET_HXX_
#define MIRROR_PROP_TREE_WEBSOCKET_HXX_
#include "Websocket.hxx"
#include <simgear/props/props.hxx>
#include <simgear/timing/timestamp.hxx>
#include <vector>
#include <memory>
namespace flightgear {
namespace http {
class MirrorTreeListener;
class MirrorPropertyTreeWebsocket : public Websocket
{
public:
MirrorPropertyTreeWebsocket(const std::string& path);
~MirrorPropertyTreeWebsocket() override;
void close() override;
void handleRequest(const HTTPRequest & request, WebsocketWriter & writer) override;
void poll(WebsocketWriter & writer) override;
private:
void checkNodeExists();
friend class MirrorTreeListener;
std::string _rootPath;
SGPropertyNode_ptr _subtreeRoot;
std::unique_ptr<MirrorTreeListener> _listener;
int _minSendInterval;
SGTimeStamp _lastSendTime;
};
}
}
#endif /* MIRROR_PROP_TREE_WEBSOCKET_HXX_ */

View File

@@ -0,0 +1,382 @@
// NavdbUriHandler.cxx -- Access the nav database
//
// Written by Torsten Dreyer, started April 2014.
//
// Copyright (C) 2014 Torsten Dreyer
//
// 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.
#include "NavdbUriHandler.hxx"
#include <simgear/debug/logstream.hxx>
#include <Navaids/navrecord.hxx>
#include <Airports/airport.hxx>
#include <ATC/CommStation.hxx>
#include <cJSON.h>
using std::string;
namespace flightgear {
namespace http {
static cJSON * createPositionArray(double x, double y, double z)
{
cJSON * p = cJSON_CreateArray();
cJSON_AddItemToArray(p, cJSON_CreateNumber(x));
cJSON_AddItemToArray(p, cJSON_CreateNumber(y));
cJSON_AddItemToArray(p, cJSON_CreateNumber(z));
return p;
}
static cJSON * createPositionArray(double x, double y)
{
cJSON * p = cJSON_CreateArray();
cJSON_AddItemToArray(p, cJSON_CreateNumber(x));
cJSON_AddItemToArray(p, cJSON_CreateNumber(y));
return p;
}
static cJSON * createLOCGeometry(FGNavRecord * navRecord)
{
assert( navRecord != NULL );
cJSON * geometry = cJSON_CreateObject();
int range = navRecord->get_range();
double width = navRecord->localizerWidth();
double course = navRecord->get_multiuse();
double px[4];
double py[4];
px[0] = navRecord->longitude();
py[0] = navRecord->latitude();
for (int i = -1; i <= +1; i++) {
double c = SGMiscd::normalizeAngle((course + 180 + i * width / 2) * SG_DEGREES_TO_RADIANS);
SGGeoc geoc = SGGeoc::fromGeod(navRecord->geod());
SGGeod p2 = SGGeod::fromGeoc(geoc.advanceRadM(c, range * SG_NM_TO_METER));
px[i + 2] = p2.getLongitudeDeg();
py[i + 2] = p2.getLatitudeDeg();
}
// Add three lines: centerline, left and right edge
cJSON_AddItemToObject(geometry, "type", cJSON_CreateString("MultiLineString"));
cJSON * coordinates = cJSON_CreateArray();
cJSON_AddItemToObject(geometry, "coordinates", coordinates);
for (int i = 1; i < 4; i++) {
cJSON * line = cJSON_CreateArray();
cJSON_AddItemToArray(coordinates, line);
cJSON_AddItemToArray(line, createPositionArray(px[0], py[0]));
cJSON_AddItemToArray(line, createPositionArray(px[i], py[i]));
}
return geometry;
}
static cJSON * createPointGeometry(FGPositioned * positioned )
{
cJSON * geometry = cJSON_CreateObject();
cJSON_AddItemToObject(geometry, "type", cJSON_CreateString("Point"));
cJSON_AddItemToObject(geometry, "coordinates",
createPositionArray(positioned ->longitude(), positioned->latitude(), positioned->elevationM()));
return geometry;
}
static cJSON * createRunwayPolygon( FGRunwayBase * rwy )
{
cJSON * polygon = cJSON_CreateObject();
cJSON_AddItemToObject(polygon, "type", cJSON_CreateString("Polygon"));
cJSON * coordinates = cJSON_CreateArray();
cJSON_AddItemToObject(polygon, "coordinates", coordinates );
cJSON * linearRing = cJSON_CreateArray();
cJSON_AddItemToArray( coordinates, linearRing );
// compute the four corners of the runway
SGGeod p1 = rwy->pointOffCenterline( 0.0, rwy->widthM()/2 );
SGGeod p2 = rwy->pointOffCenterline( 0.0, -rwy->widthM()/2 );
SGGeod p3 = rwy->pointOffCenterline( rwy->lengthM(), -rwy->widthM()/2 );
SGGeod p4 = rwy->pointOffCenterline( rwy->lengthM(), rwy->widthM()/2 );
cJSON_AddItemToArray( linearRing, createPositionArray(p1.getLongitudeDeg(), p1.getLatitudeDeg()) );
cJSON_AddItemToArray( linearRing, createPositionArray(p2.getLongitudeDeg(), p2.getLatitudeDeg()) );
cJSON_AddItemToArray( linearRing, createPositionArray(p3.getLongitudeDeg(), p3.getLatitudeDeg()) );
cJSON_AddItemToArray( linearRing, createPositionArray(p4.getLongitudeDeg(), p4.getLatitudeDeg()) );
// close the ring
cJSON_AddItemToArray( linearRing, createPositionArray(p1.getLongitudeDeg(), p1.getLatitudeDeg()) );
return polygon;
}
static cJSON * createAirportGeometry(FGAirport * airport )
{
assert( airport != NULL );
FGRunwayList runways = airport->getRunwaysWithoutReciprocals();
if( runways.empty() ) {
// no runways? Create a Point geometry
return createPointGeometry( airport );
}
cJSON * geometry = cJSON_CreateObject();
// if there are runways, create a geometry collection
cJSON_AddItemToObject(geometry, "type", cJSON_CreateString("GeometryCollection"));
cJSON * geometryCollection = cJSON_CreateArray();
cJSON_AddItemToObject(geometry, "geometries", geometryCollection);
// the first item is the aerodrome reference point
cJSON_AddItemToArray( geometryCollection, createPointGeometry(airport) );
// followed by the runway polygons
for( FGRunwayList::iterator it = runways.begin(); it != runways.end(); ++it ) {
cJSON_AddItemToArray( geometryCollection, createRunwayPolygon(*it) );
}
FGTaxiwayList taxiways = airport->getTaxiways();
// followed by the taxiway polygons
for( FGTaxiwayList::iterator it = taxiways.begin(); it != taxiways.end(); ++it ) {
cJSON_AddItemToArray( geometryCollection, createRunwayPolygon(*it) );
}
return geometry;
}
static cJSON * createGeometryFor(FGPositioned * positioned)
{
switch( positioned->type() ) {
case FGPositioned::LOC:
case FGPositioned::ILS:
return createLOCGeometry( dynamic_cast<FGNavRecord*>(positioned) );
case FGPositioned::AIRPORT:
return createAirportGeometry( dynamic_cast<FGAirport*>(positioned) );
default:
return createPointGeometry( positioned );
}
}
static void addAirportProperties(cJSON * json, FGAirport * airport )
{
if( NULL == airport ) return;
double longestRunwayLength = 0.0;
double longestRunwayHeading = 0.0;
const char * longestRunwaySurface = "";
cJSON_AddItemToObject(json, "name", cJSON_CreateString(airport->getName().c_str()));
cJSON * runwaysJson = cJSON_CreateArray();
cJSON_AddItemToObject(json, "runways", runwaysJson);
FGRunwayList runways = airport->getRunways();
for( FGRunwayList::iterator it = runways.begin(); it != runways.end(); ++it ) {
FGRunway * runway = *it;
cJSON * runwayJson = cJSON_CreateObject();
cJSON_AddItemToArray( runwaysJson, runwayJson );
cJSON_AddItemToObject(runwayJson, "id", cJSON_CreateString(runway->ident().c_str()));
cJSON_AddItemToObject(runwayJson, "length_m", cJSON_CreateNumber(runway->lengthM()));
cJSON_AddItemToObject(runwayJson, "width_m", cJSON_CreateNumber(runway->widthM()));
cJSON_AddItemToObject(runwayJson, "surface", cJSON_CreateString(runway->surfaceName()));
cJSON_AddItemToObject(runwayJson, "heading_deg", cJSON_CreateNumber(runway->headingDeg()));
double d = runway->displacedThresholdM();
if( d > .0 )
cJSON_AddItemToObject(runwayJson, "dispacedThreshold_m", cJSON_CreateNumber(d));
d = runway->stopwayM();
if( d > .0 )
cJSON_AddItemToObject(runwayJson, "stopway_m", cJSON_CreateNumber(d));
if( runway->lengthM() > longestRunwayLength ) {
longestRunwayLength = runway->lengthM();
longestRunwayHeading = runway->headingDeg();
longestRunwaySurface = runway->surfaceName();
}
}
cJSON_AddItemToObject(json, "longestRwyLength_m", cJSON_CreateNumber(longestRunwayLength));
cJSON_AddItemToObject(json, "longestRwyHeading_deg", cJSON_CreateNumber(longestRunwayHeading));
cJSON_AddItemToObject(json, "longestRwySurface", cJSON_CreateString(longestRunwaySurface));
if( airport->getMetar() ) {
cJSON_AddItemToObject(json, "metar", cJSON_CreateTrue());
}
cJSON * commsJson = cJSON_CreateArray();
cJSON_AddItemToObject(json, "comm", commsJson);
flightgear::CommStationList comms = airport->commStations();
for( flightgear::CommStationList::iterator it = comms.begin(); it != comms.end(); ++it ) {
flightgear::CommStation * comm = *it;
cJSON * commJson = cJSON_CreateObject();
cJSON_AddItemToArray( commsJson, commJson );
cJSON_AddItemToObject(commJson, "id", cJSON_CreateString(comm->ident().c_str()));
cJSON_AddItemToObject(commJson, "mhz", cJSON_CreateNumber(comm->freqMHz()));
}
}
static void addNAVProperties(cJSON * json, FGNavRecord * navRecord )
{
if( NULL == navRecord ) return;
cJSON_AddItemToObject(json, "range_nm", cJSON_CreateNumber(navRecord->get_range()));
cJSON_AddItemToObject(json, "frequency", cJSON_CreateNumber((double) navRecord->get_freq() / 100.0));
switch (navRecord->type()) {
case FGPositioned::ILS:
case FGPositioned::LOC:
cJSON_AddItemToObject(json, "localizer-course", cJSON_CreateNumber(navRecord->get_multiuse()));
break;
case FGPositioned::VOR:
cJSON_AddItemToObject(json, "variation", cJSON_CreateNumber(navRecord->get_multiuse()));
break;
default:
break;
}
}
static cJSON * createPropertiesFor(FGPositioned * positioned)
{
cJSON * properties = cJSON_CreateObject();
cJSON_AddItemToObject(properties, "name", cJSON_CreateString(positioned->name().c_str()));
// also add id to properties
cJSON_AddItemToObject(properties, "id", cJSON_CreateString(positioned->ident().c_str()));
cJSON_AddItemToObject(properties, "type", cJSON_CreateString(positioned->typeString()));
cJSON_AddItemToObject(properties, "elevation-m", cJSON_CreateNumber(positioned->elevationM()));
addNAVProperties( properties, dynamic_cast<FGNavRecord*>(positioned) );
addAirportProperties( properties, dynamic_cast<FGAirport*>(positioned) );
return properties;
}
static cJSON * createFeatureFor(FGPositioned * positioned)
{
cJSON * feature = cJSON_CreateObject();
// A GeoJSON object with the type "Feature" is a feature object.
cJSON_AddItemToObject(feature, "type", cJSON_CreateString("Feature"));
// A feature object must have a member with the name "geometry".
// The value of the geometry member is a geometry object as defined above or a JSON null value.
cJSON_AddItemToObject(feature, "geometry", createGeometryFor(positioned));
// A feature object must have a member with the name "properties".
// The value of the properties member is an object (any JSON object or a JSON null value).
cJSON_AddItemToObject(feature, "properties", createPropertiesFor(positioned));
// If a feature has a commonly used identifier, that identifier should be included
// as a member of the feature object with the name "id".
cJSON_AddItemToObject(feature, "id", cJSON_CreateString(positioned->ident().c_str()));
return feature;
}
bool NavdbUriHandler::handleRequest(const HTTPRequest & request, HTTPResponse & response, Connection * connection)
{
response.Header["Content-Type"] = "application/json; charset=UTF-8";
response.Header["Access-Control-Allow-Origin"] = "*";
response.Header["Access-Control-Allow-Methods"] = "OPTIONS, GET";
response.Header["Access-Control-Allow-Headers"] = "Origin, Accept, Content-Type, X-Requested-With, X-CSRF-Token";
if( request.Method == "OPTIONS" ){
return true; // OPTIONS only needs the headers
}
if( request.Method != "GET" ){
response.Header["Allow"] = "OPTIONS, GET";
response.StatusCode = 405;
response.Content = "{}";
return true;
}
bool indent = request.RequestVariables.get("i") == "y";
string query = request.RequestVariables.get("q");
FGPositionedList result;
if (query == "findWithinRange") {
// ?q=findWithinRange&lat=53.5&lon=10.0&range=100&type=vor,ils
double lat, lon, range = -1;
try {
lat = std::stod(request.RequestVariables.get("lat"));
lon = std::stod(request.RequestVariables.get("lon"));
range = std::stod(request.RequestVariables.get("range"));
}
catch (...) {
goto fail;
}
if (range <= 1.0) goto fail;
// In remembrance of a famous bug
SGGeod pos = SGGeod::fromDeg(lon, lat);
FGPositioned::TypeFilter filter;
try {
filter = FGPositioned::TypeFilter::fromString(request.RequestVariables.get("type"));
}
catch (...) {
goto fail;
}
result = FGPositioned::findWithinRange(pos, range, &filter);
} else if (query == "airports") {
cJSON * json = cJSON_CreateArray();
for( char ** airports = FGAirport::searchNamesAndIdents(""); *airports; airports++ ) {
cJSON_AddItemToArray(json, cJSON_CreateString(*airports));
}
char * jsonString = indent ? cJSON_Print(json) : cJSON_PrintUnformatted(json);
cJSON_Delete(json);
response.Content = jsonString;
free(jsonString);
return true;
} else if (query == "airport") {
FGAirportRef airport = FGAirport::findByIdent(request.RequestVariables.get("id"));
if( airport.valid() )
result.push_back( airport );
} else {
goto fail;
}
{ // create some GeoJSON from the result list
// GeoJSON always consists of a single object.
cJSON * geoJSON = cJSON_CreateObject();
// The GeoJSON object must have a member with the name "type".
// This member's value is a string that determines the type of the GeoJSON object.
cJSON_AddItemToObject(geoJSON, "type", cJSON_CreateString("FeatureCollection"));
// we send zero to many features - let's make it a FeatureCollection
// A GeoJSON object with the type "FeatureCollection" is a feature collection object.
// An object of type "FeatureCollection" must have a member with the name "features".
// The value corresponding to "features" is an array.
cJSON * featureCollection = cJSON_CreateArray();
cJSON_AddItemToObject(geoJSON, "features", featureCollection);
for (FGPositionedList::iterator it = result.begin(); it != result.end(); ++it) {
// Each element in the array is a feature object as defined above.
cJSON_AddItemToArray(featureCollection, createFeatureFor(*it));
}
char * jsonString = indent ? cJSON_Print(geoJSON) : cJSON_PrintUnformatted(geoJSON);
cJSON_Delete(geoJSON);
response.Content = jsonString;
free(jsonString);
}
return true;
fail: response.StatusCode = 400;
response.Content = "{ 'error': 'bad request' }";
return true;
}
} // namespace http
} // namespace flightgear

View File

@@ -0,0 +1,38 @@
// NavdbUriHandler.hxx -- Access the nav database
//
// Written by Torsten Dreyer, started April 2014.
//
// Copyright (C) 2014 Torsten Dreyer
//
// 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.
#ifndef __FG_NAVDB_URI_HANDLER_HXX
#define __FG_NAVDB_URI_HANDLER_HXX
#include "urihandler.hxx"
namespace flightgear {
namespace http {
class NavdbUriHandler : public URIHandler {
public:
NavdbUriHandler( const std::string& uri = "/navdb" ) : URIHandler( uri ) {}
virtual bool handleRequest( const HTTPRequest & request, HTTPResponse & response, Connection * connection );
};
} // namespace http
} // namespace flightgear
#endif //#define __FG_NAVDB_URI_HANDLER_HXX

View File

@@ -0,0 +1,228 @@
// PkgUriHandler.cxx -- service for the package system
//
// Written by Torsten Dreyer, started February 2015.
//
// Copyright (C) 2014 Torsten Dreyer
//
// 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.
#include "PkgUriHandler.hxx"
#include <cJSON.h>
#include <simgear/package/Root.hxx>
#include <simgear/package/Catalog.hxx>
#include <simgear/package/Delegate.hxx>
#include <simgear/package/Install.hxx>
#include <simgear/package/Package.hxx>
#include <Main/fg_props.hxx>
using std::string;
namespace flightgear {
namespace http {
/*
url: /pkg/command/args
Examples:
/pkg/path
Input:
{
command: "command",
args: {
}
}
Output:
{
}
*/
static cJSON * StringListToJson( const string_list & l )
{
cJSON * jsonArray = cJSON_CreateArray();
for( string_list::const_iterator it = l.begin(); it != l.end(); ++it )
cJSON_AddItemToArray(jsonArray, cJSON_CreateString((*it).c_str()) );
return jsonArray;
}
static cJSON * PackageToJson( simgear::pkg::Package * p )
{
cJSON * json = cJSON_CreateObject();
if( p ) {
cJSON_AddItemToObject(json, "id", cJSON_CreateString( p->id().c_str() ));
cJSON_AddItemToObject(json, "name", cJSON_CreateString( p->name().c_str() ));
cJSON_AddItemToObject(json, "description", cJSON_CreateString( p->description().c_str() ));
cJSON_AddItemToObject(json, "installed", cJSON_CreateBool( p->isInstalled() ));
cJSON_AddItemToObject(json, "thumbnails", StringListToJson( p->thumbnailUrls() ));
cJSON_AddItemToObject(json, "variants", StringListToJson( p->variants() ));
cJSON_AddItemToObject(json, "revision", cJSON_CreateNumber( p->revision() ));
cJSON_AddItemToObject(json, "fileSize", cJSON_CreateNumber( p->fileSizeBytes() ));
cJSON_AddItemToObject(json, "author", cJSON_CreateString( p->getLocalisedProp("author").c_str() ));
cJSON_AddItemToObject(json, "ratingFdm", cJSON_CreateString( p->getLocalisedProp("rating/FDM").c_str() ));
cJSON_AddItemToObject(json, "ratingCockpit", cJSON_CreateString( p->getLocalisedProp("rating/cockpit").c_str() ));
cJSON_AddItemToObject(json, "ratingModel", cJSON_CreateString( p->getLocalisedProp("rating/model").c_str() ));
cJSON_AddItemToObject(json, "ratingSystems", cJSON_CreateString( p->getLocalisedProp("rating/systems").c_str() ));
}
return json;
}
static cJSON * PackageListToJson( const simgear::pkg::PackageList & l )
{
cJSON * jsonArray = cJSON_CreateArray();
for( simgear::pkg::PackageList::const_iterator it = l.begin(); it != l.end(); ++it ) {
cJSON_AddItemToArray(jsonArray, PackageToJson(*it) );
}
return jsonArray;
}
static cJSON * CatalogToJson( simgear::pkg::Catalog * c )
{
cJSON * json = cJSON_CreateObject();
if( c ) {
cJSON_AddItemToObject(json, "id", cJSON_CreateString( c->id().c_str() ));
std::string s = c->installRoot().utf8Str();
cJSON_AddItemToObject(json, "installRoot", cJSON_CreateString( s.c_str() ));
cJSON_AddItemToObject(json, "url", cJSON_CreateString( c->url().c_str() ));
cJSON_AddItemToObject(json, "description", cJSON_CreateString( c->description().c_str() ));
cJSON_AddItemToObject(json, "packages", PackageListToJson(c->packages()) );
cJSON_AddItemToObject(json, "needingUpdate", PackageListToJson(c->packagesNeedingUpdate()) );
cJSON_AddItemToObject(json, "installed", PackageListToJson(c->installedPackages()) );
}
return json;
}
static string PackageRootCommand( simgear::pkg::Root* packageRoot, const string & command, const string & args )
{
cJSON * json = cJSON_CreateObject();
if( command == "path" ) {
std::string p = packageRoot->path().utf8Str();
cJSON_AddItemToObject(json, "path", cJSON_CreateString( p.c_str() ));
} else if( command == "version" ) {
cJSON_AddItemToObject(json, "version", cJSON_CreateString( packageRoot->applicationVersion().c_str() ));
} else if( command == "refresh" ) {
packageRoot->refresh(true);
cJSON_AddItemToObject(json, "refresh", cJSON_CreateString( "OK" ));
} else if( command == "catalogs" ) {
cJSON * jsonArray = cJSON_CreateArray();
simgear::pkg::CatalogList catalogList = packageRoot->catalogs();
for( simgear::pkg::CatalogList::iterator it = catalogList.begin(); it != catalogList.end(); ++it ) {
cJSON_AddItemToArray(jsonArray, CatalogToJson(*it) );
}
cJSON_AddItemToObject(json, "catalogs", jsonArray );
} else if( command == "packageById" ) {
simgear::pkg::PackageRef p = packageRoot->getPackageById(args);
cJSON_AddItemToObject(json, "package", PackageToJson( p ));
} else if( command == "catalogById" ) {
simgear::pkg::CatalogRef p = packageRoot->getCatalogById(args);
cJSON_AddItemToObject(json, "catalog", CatalogToJson( p ));
} else if( command == "search" ) {
SGPropertyNode_ptr query(new SGPropertyNode);
simgear::pkg::PackageList packageList = packageRoot->packagesMatching(query);
cJSON_AddItemToObject(json, "packages", PackageListToJson(packageList) );
} else if( command == "install" ) {
simgear::pkg::PackageRef package = packageRoot->getPackageById(args);
if( NULL == package ) {
SG_LOG(SG_NETWORK,SG_WARN,"Can't install package '" << args << "', package not found" );
cJSON_Delete( json );
return string("");
}
package->existingInstall();
} else {
SG_LOG( SG_NETWORK,SG_WARN, "Unhandled pkg command : '" << command << "'" );
cJSON_Delete( json );
return string("");
}
char * jsonString = cJSON_PrintUnformatted( json );
string reply(jsonString);
free( jsonString );
cJSON_Delete( json );
return reply;
}
static string findCommand( const string & uri, string & outArgs )
{
size_t n = uri.find_first_of('/');
if( n == string::npos ) outArgs = string("");
else outArgs = uri.substr( n+1 );
return uri.substr( 0, n );
}
bool PkgUriHandler::handleRequest( const HTTPRequest & request, HTTPResponse & response, Connection * connection )
{
response.Header["Content-Type"] = "application/json; charset=UTF-8";
response.Header["Access-Control-Allow-Origin"] = "*";
response.Header["Access-Control-Allow-Methods"] = "OPTIONS, GET, POST";
response.Header["Access-Control-Allow-Headers"] = "Origin, Accept, Content-Type, X-Requested-With, X-CSRF-Token";
if( request.Method == "OPTIONS" ){
return true; // OPTIONS only needs the headers
}
simgear::pkg::Root* packageRoot = globals->packageRoot();
if( NULL == packageRoot ) {
SG_LOG( SG_NETWORK,SG_WARN, "NO PackageRoot" );
response.StatusCode = 500;
response.Content = "{}";
return true;
}
string argString;
string command = findCommand( string(request.Uri).substr(getUri().size()), argString );
SG_LOG(SG_NETWORK,SG_INFO, "Request is for command '" << command << "' with arg='" << argString << "'" );
if( request.Method == "GET" ){
} else if( request.Method == "POST" ) {
} else {
SG_LOG(SG_NETWORK,SG_INFO, "PkgUriHandler: invalid request method '" << request.Method << "'" );
response.Header["Allow"] = "OPTIONS, GET, POST";
response.StatusCode = 405;
response.Content = "{}";
return true;
}
response.Content = PackageRootCommand( packageRoot, command, argString );
if( response.Content.empty() ) {
response.StatusCode = 404;
response.Content = "{}";
}
return true;
}
} // namespace http
} // namespace flightgear

View File

@@ -0,0 +1,40 @@
// PkgUriHandler.hxx -- service for the package system
//
// Written by Torsten Dreyer, started February 2015.
//
// Copyright (C) 2014 Torsten Dreyer
//
// 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.
#ifndef __FG_PKG_URI_HANDLER_HXX
#define __FG_PKG_URI_HANDLER_HXX
#include "urihandler.hxx"
#include <simgear/props/props.hxx>
namespace flightgear {
namespace http {
class PkgUriHandler : public URIHandler {
public:
PkgUriHandler( const std::string& uri = "/pkg/" ) : URIHandler( uri ) {}
virtual bool handleRequest( const HTTPRequest & request, HTTPResponse & response, Connection * connection );
private:
};
} // namespace http
} // namespace flightgear
#endif //#define __FG_PKG_URI_HANDLER_HXX

View File

@@ -0,0 +1,105 @@
// PropertyChangeObserver.cxx -- Watch properties for changes
//
// Written by Torsten Dreyer, started April 2014.
//
// Copyright (C) 2014 Torsten Dreyer
//
// 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.
#include "PropertyChangeObserver.hxx"
#include <Main/fg_props.hxx>
using std::string;
namespace flightgear {
namespace http {
PropertyChangeObserver::PropertyChangeObserver()
{
}
PropertyChangeObserver::~PropertyChangeObserver()
{
//
}
void PropertyChangeObserver::check()
{
for (Entries_t::iterator it = _entries.begin(); it != _entries.end(); ++it) {
if (!(*it)->_node.isShared()) {
// node is no longer used but by us - remove the entry
it = _entries.erase(it);
continue;
}
if(!(*it)->_changed ) {
(*it)->_changed = (*it)->_prevValue != (*it)->_node->getStringValue();
if ((*it)->_changed)
(*it)->_prevValue = (*it)->_node->getStringValue();
}
}
}
void PropertyChangeObserver::uncheck()
{
for (Entries_t::iterator it = _entries.begin(); it != _entries.end(); ++it) {
(*it)->_changed = false;
}
}
const SGPropertyNode_ptr PropertyChangeObserver::addObservation( const string propertyName)
{
for (Entries_t::iterator it = _entries.begin(); it != _entries.end(); ++it) {
if (propertyName == (*it)->_node->getPath(true) ) {
// if a new observer is added to a property, mark it as changed to ensure the observer
// gets notified on initial call. This also causes a notification for all other observers of this
// property.
(*it)->_changed = true;
return (*it)->_node;
}
}
try {
PropertyChangeObserverEntryRef entry = new PropertyChangeObserverEntry();
entry->_node = fgGetNode( propertyName, true );
_entries.push_back( entry );
return entry->_node;
}
catch( string & s ) {
SG_LOG(SG_NETWORK,SG_WARN,"httpd: can't observer '" << propertyName << "'. Invalid name." );
}
SGPropertyNode_ptr empty;
return empty;
}
bool PropertyChangeObserver::isChangedValue(const SGPropertyNode_ptr node)
{
for (Entries_t::iterator it = _entries.begin(); it != _entries.end(); ++it) {
PropertyChangeObserverEntryRef entry = *it;
if( entry->_node == node && entry->_changed ) {
return true;
}
}
return false;
}
} // namespace http
} // namespace flightgear

View File

@@ -0,0 +1,64 @@
// PropertyChangeObserver.hxx -- Watch properties for changes
//
// Written by Torsten Dreyer, started April 2014.
//
// Copyright (C) 2014 Torsten Dreyer
//
// 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.
#ifndef PROPERTYCHANGEOBSERVER_HXX_
#define PROPERTYCHANGEOBSERVER_HXX_
#include <simgear/props/props.hxx>
#include <string>
#include <vector>
namespace flightgear {
namespace http {
struct PropertyChangeObserverEntry : public SGReferenced {
PropertyChangeObserverEntry()
: _changed(true)
{
}
SGPropertyNode_ptr _node;
std::string _prevValue;
bool _changed;
};
typedef SGSharedPtr<PropertyChangeObserverEntry> PropertyChangeObserverEntryRef;
class PropertyChangeObserver {
public:
PropertyChangeObserver();
virtual ~PropertyChangeObserver();
const SGPropertyNode_ptr addObservation( const std::string propertyName);
bool isChangedValue(const SGPropertyNode_ptr node);
void check();
void uncheck();
void clear() { _entries.clear(); }
private:
typedef std::vector<PropertyChangeObserverEntryRef> Entries_t;
Entries_t _entries;
};
} // namespace http
} // namespace flightgear
#endif /* PROPERTYCHANGEOBSERVER_HXX_ */

View File

@@ -0,0 +1,260 @@
// PropertyChangeWebsocket.cxx -- A websocket for propertychangelisteners
//
// Written by Torsten Dreyer, started April 2014.
//
// Copyright (C) 2014 Torsten Dreyer
//
// 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.
#include "PropertyChangeWebsocket.hxx"
#include "PropertyChangeObserver.hxx"
#include "jsonprops.hxx"
#include <simgear/debug/logstream.hxx>
#include <simgear/props/props.hxx>
#include <simgear/structure/commands.hxx>
#include <simgear/props/props_io.hxx>
#include <Main/globals.hxx>
#include <Main/fg_props.hxx>
#include <cJSON.h>
namespace flightgear {
namespace http {
using std::string;
static unsigned nextid = 0;
static void setPropertyFromJson(SGPropertyNode_ptr prop, cJSON * json)
{
if (!prop) return;
if ( NULL == json ) return;
switch ( json->type ) {
case cJSON_String:
prop->setStringValue(json->valuestring);
break;
case cJSON_Number:
prop->setDoubleValue(json->valuedouble);
break;
case cJSON_True:
prop->setBoolValue(true);
break;
case cJSON_False:
prop->setBoolValue(false);
break;
default:
return;
}
}
static void handleSetCommand(const string_list& nodes, cJSON* json, WebsocketWriter &writer)
{
cJSON * value = cJSON_GetObjectItem(json, "value");
if ( NULL != value ) {
if (nodes.size() > 1) {
SG_LOG(SG_NETWORK, SG_WARN, "httpd: WS set: insufficent values for nodes:" << nodes.size());
return;
}
SGPropertyNode_ptr n = fgGetNode(nodes.front());
if (!n) {
SG_LOG(SG_NETWORK, SG_WARN, "httpd: set '" << nodes.front() << "' not found");
return;
}
setPropertyFromJson(n, value);
return;
}
cJSON * values = cJSON_GetObjectItem(json, "values");
if ( ( NULL == values ) || ( static_cast<size_t>(cJSON_GetArraySize(values)) != nodes.size()) ) {
SG_LOG(SG_NETWORK, SG_WARN, "httpd: WS set: mismatched nodes/values sizes:" << nodes.size());
return;
}
string_list::const_iterator it;
int i=0;
for (it = nodes.begin(); it != nodes.end(); ++it, ++i) {
SGPropertyNode_ptr n = fgGetNode(*it);
if (!n) {
SG_LOG(SG_NETWORK, SG_WARN, "httpd: get '" << *it << "' not found");
return;
}
setPropertyFromJson(n, cJSON_GetArrayItem(values, i));
} // of nodes iteration
}
static void handleExecCommand(cJSON* json)
{
cJSON* name = cJSON_GetObjectItem(json, "fgcommand");
if ((NULL == name )|| (NULL == name->valuestring)) {
SG_LOG(SG_NETWORK, SG_WARN, "httpd: exec: no fgcommand name");
return;
}
SGPropertyNode_ptr arg(new SGPropertyNode);
JSON::addChildrenToProp( json, arg );
globals->get_commands()->execute(name->valuestring, arg, nullptr);
}
PropertyChangeWebsocket::PropertyChangeWebsocket(PropertyChangeObserver * propertyChangeObserver)
: id(++nextid),
_propertyChangeObserver(propertyChangeObserver),
_minTriggerInterval(fgGetDouble("/sim/http/property-websocket/update-interval-secs", 0.05)), // default 20Hz
_lastTrigger(-1000)
{
}
PropertyChangeWebsocket::~PropertyChangeWebsocket()
{
}
void PropertyChangeWebsocket::close()
{
SG_LOG(SG_NETWORK, SG_INFO, "closing PropertyChangeWebsocket #" << id);
_watchedNodes.clear();
}
void PropertyChangeWebsocket::handleGetCommand(const string_list& nodes, WebsocketWriter &writer)
{
double t = fgGetDouble("/sim/time/elapsed-sec");
string_list::const_iterator it;
for (it = nodes.begin(); it != nodes.end(); ++it) {
SGPropertyNode_ptr n = fgGetNode(*it);
if (!n) {
SG_LOG(SG_NETWORK, SG_WARN, "httpd: get '" << *it << "' not found");
return;
}
writer.writeText( JSON::toJsonString( false, n, 0, t ) );
} // of nodes iteration
}
void PropertyChangeWebsocket::handleRequest(const HTTPRequest & request, WebsocketWriter &writer)
{
if (request.Content.empty()) return;
/*
* allowed JSON is
{
command : 'addListener',
nodes : [
'/bar/baz',
'/foo/bar'
],
node: '/bax/foo'
}
*/
cJSON * json = cJSON_Parse(request.Content.c_str());
if ( NULL != json) {
string command;
cJSON * j = cJSON_GetObjectItem(json, "command");
if ( NULL != j && NULL != j->valuestring) {
command = j->valuestring;
}
// handle a single node name, or an array of them
string_list nodeNames;
j = cJSON_GetObjectItem(json, "node");
if ( NULL != j && NULL != j->valuestring) {
nodeNames.push_back(simgear::strutils::strip(string(j->valuestring)));
}
cJSON * nodes = cJSON_GetObjectItem(json, "nodes");
if ( NULL != nodes) {
for (int i = 0; i < cJSON_GetArraySize(nodes); i++) {
cJSON * node = cJSON_GetArrayItem(nodes, i);
if ( NULL == node) continue;
if ( NULL == node->valuestring) continue;
nodeNames.push_back(simgear::strutils::strip(string(node->valuestring)));
}
}
if (command == "get") {
handleGetCommand(nodeNames, writer);
} else if (command == "set") {
handleSetCommand(nodeNames, json, writer);
} else if (command == "exec") {
handleExecCommand(json);
} else {
string_list::const_iterator it;
for (it = nodeNames.begin(); it != nodeNames.end(); ++it) {
_watchedNodes.handleCommand(command, *it, _propertyChangeObserver);
}
}
cJSON_Delete(json);
}
}
void PropertyChangeWebsocket::poll(WebsocketWriter & writer)
{
double now = fgGetDouble("/sim/time/elapsed-sec");
if( _minTriggerInterval > .0 ) {
if( now - _lastTrigger <= _minTriggerInterval )
return;
_lastTrigger = now;
}
for (WatchedNodesList::iterator it = _watchedNodes.begin(); it != _watchedNodes.end(); ++it) {
SGPropertyNode_ptr node = *it;
string newValue;
if (_propertyChangeObserver->isChangedValue(node)) {
string out = JSON::toJsonString( false, node, 0, now );
SG_LOG(SG_NETWORK, SG_DEBUG, "PropertyChangeWebsocket::poll() new Value for " << node->getPath(true) << " '" << node->getStringValue() << "' #" << id << ": " << out );
writer.writeText( out );
}
}
}
void PropertyChangeWebsocket::WatchedNodesList::handleCommand(const string & command, const string & node,
PropertyChangeObserver * propertyChangeObserver)
{
if (command == "addListener") {
for (iterator it = begin(); it != end(); ++it) {
if (node == (*it)->getPath(true)) {
SG_LOG(SG_NETWORK, SG_WARN, "httpd: " << command << " '" << node << "' ignored (duplicate)");
return; // dupliate
}
}
SGPropertyNode_ptr n = propertyChangeObserver->addObservation(node);
if (n.valid()) push_back(n);
SG_LOG(SG_NETWORK, SG_INFO, "httpd: " << command << " '" << node << "' success");
} else if (command == "removeListener") {
for (iterator it = begin(); it != end(); ++it) {
if (node == (*it)->getPath(true)) {
this->erase(it);
SG_LOG(SG_NETWORK, SG_INFO, "httpd: " << command << " '" << node << "' success");
return;
}
}
SG_LOG(SG_NETWORK, SG_WARN, "httpd: " << command << " '" << node << "' ignored (not found)");
}
}
} // namespace http
} // namespace flightgear

View File

@@ -0,0 +1,61 @@
// PropertyChangeWebsocket.hxx -- A websocket for propertychangelisteners
//
// Written by Torsten Dreyer, started April 2014.
//
// Copyright (C) 2014 Torsten Dreyer
//
// 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.
#ifndef PROPERTYCHANGEWEBSOCKET_HXX_
#define PROPERTYCHANGEWEBSOCKET_HXX_
#include "Websocket.hxx"
#include <simgear/props/props.hxx>
#include <vector>
namespace flightgear {
namespace http {
class PropertyChangeObserver;
class PropertyChangeWebsocket: public Websocket {
public:
PropertyChangeWebsocket(PropertyChangeObserver * propertyChangeObserver);
virtual ~PropertyChangeWebsocket();
virtual void close();
virtual void handleRequest(const HTTPRequest & request, WebsocketWriter & writer);
virtual void poll(WebsocketWriter & writer);
private:
unsigned id;
PropertyChangeObserver * _propertyChangeObserver;
void handleGetCommand(const string_list& nodes, WebsocketWriter &writer);
class WatchedNodesList: public std::vector<SGPropertyNode_ptr> {
public:
void handleCommand(const std::string & command, const std::string & node, PropertyChangeObserver * propertyChangeObserver);
};
WatchedNodesList _watchedNodes;
double _minTriggerInterval;
double _lastTrigger;
};
}
}
#endif /* PROPERTYCHANGEWEBSOCKET_HXX_ */

View File

@@ -0,0 +1,411 @@
// PropertyUriHandler.cxx -- a web form interface to the property tree
//
// Written by Torsten Dreyer, started April 2014.
//
// Copyright (C) 2014 Torsten Dreyer
//
// 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.
#include "PropertyUriHandler.hxx"
#include "SimpleDOM.hxx"
#include <Main/fg_props.hxx>
#include <simgear/debug/logstream.hxx>
#include <simgear/misc/strutils.hxx>
#include <vector>
#include <map>
#include <algorithm>
#include <cstring>
using std::string;
using std::map;
using std::vector;
namespace flightgear {
namespace http {
// copied from http://stackoverflow.com/a/24315631
static void ReplaceAll(std::string & str, const std::string & from, const std::string & to)
{
size_t start_pos = 0;
while((start_pos = str.find(from, start_pos)) != std::string::npos) {
str.replace(start_pos, from.length(), to);
start_pos += to.length(); // Handles case where 'to' is a substring of 'from'
}
}
static const std::string specialChars[][2] = {
{ "&", "&amp;" },
{ "\"", "&quot;" },
{ "'", "&#039;" },
{ "<", "&lt;" },
{ ">", "&gt;" },
};
static inline std::string htmlSpecialChars( const std::string & s )
{
string reply = s;
for( size_t i = 0; i < sizeof(specialChars)/sizeof(specialChars[0]); ++i )
ReplaceAll( reply, specialChars[i][0], specialChars[i][1] );
return reply;
}
class SortedChilds : public simgear::PropertyList {
public:
SortedChilds( SGPropertyNode_ptr node ) {
for (int i = 0; i < node->nChildren(); i++)
push_back(node->getChild(i));
std::sort(begin(), end(), CompareNodes());
}
private:
class CompareNodes {
public:
bool operator() (const SGPropertyNode *a, const SGPropertyNode *b) const {
int r = strcmp(a->getNameString().c_str(), b->getNameString().c_str());
return r ? r < 0 : a->getIndex() < b->getIndex();
}
};
};
static const char * getPropertyTypeString( simgear::props::Type type )
{
switch( type ) {
case simgear::props::NONE: return "";
case simgear::props::ALIAS: return "alias";
case simgear::props::BOOL: return "bool";
case simgear::props::INT: return "int";
case simgear::props::LONG: return "long";
case simgear::props::FLOAT: return "float";
case simgear::props::DOUBLE: return "double";
case simgear::props::STRING: return "string";
case simgear::props::UNSPECIFIED: return "unspecified";
case simgear::props::EXTENDED: return "extended";
case simgear::props::VEC3D: return "vec3d";
case simgear::props::VEC4D: return "vec4d";
default: return "?";
}
}
DOMElement * createHeader( const string & prefix, const string & propertyPath )
{
using namespace simgear::strutils;
string path = prefix;
DOMNode * root = new DOMNode( "div" );
root->setAttribute( "id", "breadcrumb" );
DOMNode * headline = new DOMNode( "h3" );
root->addChild( headline );
headline->addChild( new DOMTextElement("FlightGear Property Browser") );
{
DOMNode * div = new DOMNode("div");
root->addChild(div);
div->addChild( new DOMNode("span"))->addChild( new DOMTextElement("Path:"));
div->addChild( new DOMNode("span"))->addChild( new DOMTextElement( propertyPath ) );
div->addChild( new DOMNode("a"))->
setAttribute("href",string("/json/")+propertyPath+"?i=y")->
addChild( new DOMTextElement( "As JSON" ) );
}
DOMNode * breadcrumb = new DOMNode("ul");
root->addChild( breadcrumb );
DOMNode * li = new DOMNode("li");
breadcrumb->addChild( li );
DOMNode * a = new DOMNode("a");
li->addChild( a );
a->setAttribute( "href", path );
a->addChild( new DOMTextElement( "[root]" ) );
string_list items = split( propertyPath, "/" );
for( string_list::iterator it = items.begin(); it != items.end(); ++it ) {
if( (*it).empty() ) continue;
path.append( *it ).append( "/" );
li = new DOMNode("li");
breadcrumb->addChild( li );
a = new DOMNode("a");
li->addChild( a );
a->setAttribute( "href", path );
a->addChild( new DOMTextElement( (*it) ) );
}
return root;
}
static DOMNode * createLabeledRadioButton( const char * label, const std::string & name, bool checked )
{
DOMNode * root = new DOMNode("span");
root->setAttribute( "class", "radiobutton-container" );
root->addChild( new DOMNode("span"))->addChild( new DOMTextElement(label) );
DOMNode * radio = root->addChild(new DOMNode( "input" ))
->setAttribute( "type", "radio" )
->setAttribute( "name", name )
->setAttribute( "value", label );
if( checked )
radio->setAttribute( "checked", "checked" );
return root;
}
static DOMElement * renderPropertyValueElement( SGPropertyNode_ptr node )
{
string value = node->getStringValue();
int len = value.length();
if( len < 15 ) len = 15;
DOMNode * root;
if( node->getType() == simgear::props::BOOL ) {
root = new DOMNode( "span" );
root->addChild( createLabeledRadioButton( "true", node->getDisplayName(), node->getBoolValue() ));
root->addChild( createLabeledRadioButton( "false", node->getDisplayName(), !node->getBoolValue() ));
} else if( len < 60 ) {
root = new DOMNode( "input" );
root->setAttribute( "type", "text" );
root->setAttribute( "name", node->getDisplayName() );
root->setAttribute( "value", htmlSpecialChars(value) );
root->setAttribute( "size", std::to_string(len) );
root->setAttribute( "maxlength", "2047" );
} else {
int rows = (len / 60)+1;
int cols = 60;
root = new DOMNode( "textarea" );
root->setAttribute( "name", node->getDisplayName() );
root->setAttribute( "cols", std::to_string( cols ) );
root->setAttribute( "rows", std::to_string( rows ) );
root->setAttribute( "maxlength", "2047" );
root->addChild( new DOMTextElement( htmlSpecialChars(value) ) );
}
return root;
}
bool PropertyUriHandler::handleGetRequest( const HTTPRequest & request, HTTPResponse & response, Connection * connection )
{
string propertyPath = request.Uri;
// strip the uri prefix of our handler
propertyPath = propertyPath.substr( getUri().size() );
// strip the querystring
size_t pos = propertyPath.find( '?' );
if( pos != string::npos ) {
propertyPath = propertyPath.substr( 0, pos-1 );
}
// skip trailing '/' - not very efficient but shouldn't happen too often
while( !propertyPath.empty() && propertyPath[ propertyPath.length()-1 ] == '/' )
propertyPath = propertyPath.substr(0,propertyPath.length()-1);
if( request.RequestVariables.get("submit") == "update" ) {
// update leaf
string value = request.RequestVariables.get("value");
SG_LOG(SG_NETWORK,SG_INFO, "httpd: setting " << propertyPath << " to '" << value << "'" );
try {
fgSetString( propertyPath.c_str(), value );
}
catch( string & s ) {
SG_LOG(SG_NETWORK,SG_WARN, "httpd: setting " << propertyPath << " to '" << value << "' failed: " << s );
}
}
if( request.RequestVariables.get("submit") == "set" ) {
for( HTTPRequest::StringMap::const_iterator it = request.RequestVariables.begin(); it != request.RequestVariables.end(); ++it ) {
if( it->first == "submit" ) continue;
string pp = propertyPath + "/" + it->first;
SG_LOG(SG_NETWORK,SG_INFO, "httpd: setting " << pp << " to '" << it->second << "'" );
try {
fgSetString( pp, it->second );
}
catch( string & s ) {
SG_LOG(SG_NETWORK,SG_WARN, "httpd: setting " << pp << " to '" << it->second << "' failed: " << s );
}
}
}
// build the response
DOMNode * html = new DOMNode( "html" );
html->setAttribute( "lang", "en" );
DOMNode * head = new DOMNode( "head" );
html->addChild( head );
DOMNode * e;
e = new DOMNode( "title" );
head->addChild( e );
e->addChild( new DOMTextElement( string("FlightGear Property Browser - ") + propertyPath ) );
e = new DOMNode( "link" );
head->addChild( e );
e->setAttribute( "href", "/css/props.css" );
e->setAttribute( "rel", "stylesheet" );
e->setAttribute( "type", "text/css" );
DOMNode * body = new DOMNode( "body" );
html->addChild( body );
SGPropertyNode_ptr node;
try {
node = fgGetNode( string("/") + propertyPath );
}
catch( string & s ) {
SG_LOG(SG_NETWORK,SG_WARN, "httpd: reading '" << propertyPath << "' failed: " << s );
}
if( !node.valid() ) {
DOMNode * headline = new DOMNode( "h3" );
body->addChild( headline );
headline->addChild( new DOMTextElement( "Non-existent node requested!" ) );
e = new DOMNode( "b" );
e->addChild( new DOMTextElement( propertyPath ) );
// does not exist
body->addChild( e );
response.StatusCode = 404;
} else if( node->nChildren() > 0 ) {
// Render the list of children
body->addChild( createHeader( getUri(), propertyPath ));
DOMNode * table = new DOMNode("table");
body->addChild( table );
DOMNode * tr = new DOMNode( "tr" );
table->addChild( tr );
DOMNode * th = new DOMNode( "th" );
tr->addChild( th );
th->addChild( new DOMTextElement( "&nbsp;" ) );
th = new DOMNode( "th" );
tr->addChild( th );
th->addChild( new DOMTextElement( "Property" ) );
th->setAttribute( "id", "property" );
th = new DOMNode( "th" );
tr->addChild( th );
th->addChild( new DOMTextElement( "Value" ) );
th->setAttribute( "id", "value" );
th = new DOMNode( "th" );
tr->addChild( th );
th->addChild( new DOMTextElement( "Type" ) );
th->setAttribute( "id", "type" );
SortedChilds sortedChilds( node );
for(SortedChilds::iterator it = sortedChilds.begin(); it != sortedChilds.end(); ++it ) {
tr = new DOMNode( "tr" );
table->addChild( tr );
SGPropertyNode_ptr child = *it;
string name = child->getDisplayName(true);
DOMNode * td;
// Expand Link
td = new DOMNode("td");
tr->addChild( td );
td->setAttribute( "id", "expand" );
if ( child->nChildren() > 0 ) {
DOMNode * a = new DOMNode("a");
td->addChild( a );
a->setAttribute( "href", getUri() + propertyPath + "/" + name );
a->addChild( new DOMTextElement( "(+)" ));
}
// Property Name
td = new DOMNode("td");
tr->addChild( td );
td->setAttribute( "id", "property" );
DOMNode * a = new DOMNode("a");
td->addChild( a );
a->setAttribute( "href", getUri() + propertyPath + "/" + name );
a->addChild( new DOMTextElement( name ) );
// Value
td = new DOMNode("td");
tr->addChild( td );
td->setAttribute( "id", "value" );
if ( child->nChildren() == 0 ) {
DOMNode * form = new DOMNode("form");
td->addChild( form );
form->setAttribute( "method", "GET" );
form->setAttribute( "action", getUri() + propertyPath );
e = new DOMNode( "input" );
form->addChild( e );
e->setAttribute( "type", "submit" );
e->setAttribute( "value", "set" );
e->setAttribute( "name", "submit" );
form->addChild( renderPropertyValueElement( node->getNode( name ) ) );
} else {
td->addChild( new DOMTextElement( "&nbsp;" ) );
}
// Property Type
td = new DOMNode("td");
tr->addChild( td );
td->setAttribute( "id", "type" );
td->addChild(
new DOMTextElement( getPropertyTypeString(node->getNode( name )->getType()) ) );
}
} else {
// Render a single property
body->addChild( createHeader( getUri(), propertyPath ));
e = new DOMNode( "div" );
body->addChild( e );
e->setAttribute( "id", "currentvalue" );
e->addChild( new DOMTextElement( "Current Value: " ) );
e->addChild( new DOMTextElement( htmlSpecialChars(node->getStringValue()) ) );
DOMNode * form = new DOMNode("form");
body->addChild( form );
form->setAttribute( "method", "GET" );
form->setAttribute( "action", getUri() + propertyPath );
e = new DOMNode( "input" );
form->addChild( e );
e->setAttribute( "type", "submit" );
e->setAttribute( "value", "update" );
e->setAttribute( "name", "submit" );
form->addChild( renderPropertyValueElement( node ) );
}
// Send the response
response.Content = "<!DOCTYPE html>";
response.Content.append( html->render() );
delete html;
response.Header["Content-Type"] = "text/html; charset=UTF-8";
return true;
}
} // namespace http
} // namespace flightgear

View File

@@ -0,0 +1,38 @@
// PropertyUriHandler.cxx -- a web form interface to the property tree
//
// Written by Torsten Dreyer, started April 2014.
//
// Copyright (C) 2014 Torsten Dreyer
//
// 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.
#ifndef __FG_PROPERTY_URI_HANDLER_HXX
#define __FG_PROPERTY_URI_HANDLER_HXX
#include "urihandler.hxx"
namespace flightgear {
namespace http {
class PropertyUriHandler : public URIHandler {
public:
PropertyUriHandler( const std::string& uri = "/prop/" ) : URIHandler( uri ) {}
virtual bool handleGetRequest( const HTTPRequest & request, HTTPResponse & response, Connection * connection );
};
} // namespace http
} // namespace flightgear
#endif //#define __FG_PROPERTY_URI_HANDLER_HXX

View File

@@ -0,0 +1,66 @@
// RunUriHandler.cxx -- Run a flightgear command
//
// Written by Torsten Dreyer, started April 2014.
//
// Copyright (C) 2014 Torsten Dreyer
//
// 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.
#include "RunUriHandler.hxx"
#include "jsonprops.hxx"
#include <simgear/props/props.hxx>
#include <simgear/structure/commands.hxx>
#include <Main/globals.hxx>
#include <cJSON.h>
using std::string;
namespace flightgear {
namespace http {
bool RunUriHandler::handleRequest( const HTTPRequest & request, HTTPResponse & response, Connection * connection )
{
response.Header["Content-Type"] = "text/plain";
string command = request.RequestVariables.get("value");
if( command.empty() ) {
response.StatusCode = 400;
response.Content = "command not specified";
return true;
}
SGPropertyNode_ptr args = new SGPropertyNode();
cJSON * json = cJSON_Parse( request.Content.c_str() );
JSON::toProp( json, args );
SG_LOG( SG_NETWORK, SG_INFO, "RunUriHandler("<< request.Content << "): command='" << command << "', arg='" << JSON::toJsonString(false,args,5) << "'");
cJSON_Delete( json );
if ( globals->get_commands()->execute(command.c_str(), args, nullptr) ) {
response.Content = "ok.";
return true;
}
response.Content = "command '" + command + "' failed.";
response.StatusCode = 501; // Not implemented probably suits best
SG_LOG( SG_NETWORK, SG_WARN, response.Content );
return true;
}
} // namespace http
} // namespace flightgear

View File

@@ -0,0 +1,38 @@
// RunUriHandler.hxx -- Provide screenshots via http
//
// Written by Torsten Dreyer, started April 2014.
//
// Copyright (C) 2014 Torsten Dreyer
//
// 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.
#ifndef __FG_RUN_URI_HANDLER_HXX
#define __FG_RUN_URI_HANDLER_HXX
#include "urihandler.hxx"
namespace flightgear {
namespace http {
class RunUriHandler : public URIHandler {
public:
RunUriHandler( const std::string& uri = "/run.cgi" ) : URIHandler( uri ) {}
virtual bool handleRequest( const HTTPRequest & request, HTTPResponse & response, Connection * connection );
};
} // namespace http
} // namespace flightgear
#endif //#define __FG_RUN_URI_HANDLER_HXX

View File

@@ -0,0 +1,588 @@
// ScreenshotUriHandler.cxx -- Provide screenshots via http
//
// Started by Curtis Olson, started June 2001.
// osg support written by James Turner
// Ported to new httpd infrastructure by Torsten Dreyer
//
// Copyright (C) 2014 Torsten Dreyer
//
// 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.
#include "ScreenshotUriHandler.hxx"
#include <osgDB/Registry>
#include <osgDB/ReaderWriter>
#include <osgUtil/SceneView>
#include <osgViewer/Viewer>
#include <Canvas/canvas_mgr.hxx>
#include <simgear/canvas/Canvas.hxx>
#include <simgear/threads/SGQueue.hxx>
#include <simgear/structure/Singleton.hxx>
#include <Main/globals.hxx>
#include <Viewer/renderer.hxx>
#include <queue>
using std::string;
using std::vector;
using std::list;
namespace sc = simgear::canvas;
namespace flightgear {
namespace http {
///////////////////////////////////////////////////////////////////////////
class ImageReadyListener {
public:
virtual void imageReady(osg::ref_ptr<osg::Image>) = 0;
virtual ~ImageReadyListener()
{
}
};
class StringReadyListener {
public:
virtual void stringReady(const std::string &) = 0;
virtual ~StringReadyListener()
{
}
};
struct ImageCompressionTask {
StringReadyListener * stringReadyListener;
string format;
osg::ref_ptr<osg::Image> image;
ImageCompressionTask()
{
stringReadyListener = NULL;
}
ImageCompressionTask(const ImageCompressionTask & other)
{
stringReadyListener = other.stringReadyListener;
format = other.format;
image = other.image;
}
ImageCompressionTask & operator =(const ImageCompressionTask & other)
{
stringReadyListener = other.stringReadyListener;
format = other.format;
image = other.image;
return *this;
}
};
class ImageCompressor: public OpenThreads::Thread {
public:
ImageCompressor()
{
}
virtual void run();
void addTask(ImageCompressionTask & task);
private:
typedef SGBlockingQueue<ImageCompressionTask> TaskList;
TaskList _tasks;
};
typedef simgear::Singleton<ImageCompressor> ImageCompressorSingleton;
void ImageCompressor::run()
{
osg::ref_ptr<osgDB::ReaderWriter::Options> options = new osgDB::ReaderWriter::Options("JPEG_QUALITY 80 PNG_COMPRESSION 9");
SG_LOG(SG_NETWORK, SG_DEBUG, "ImageCompressor is running");
for (;;) {
ImageCompressionTask task = _tasks.pop();
SG_LOG(SG_NETWORK, SG_DEBUG, "ImageCompressor has an image");
if ( NULL != task.stringReadyListener) {
SG_LOG(SG_NETWORK, SG_DEBUG, "ImageCompressor checking for writer for " << task.format);
osgDB::ReaderWriter* writer = osgDB::Registry::instance()->getReaderWriterForExtension(task.format);
if (!writer)
continue;
SG_LOG(SG_NETWORK, SG_DEBUG, "ImageCompressor compressing to " << task.format);
std::stringstream outputStream;
osgDB::ReaderWriter::WriteResult wr;
wr = writer->writeImage(*task.image, outputStream, options);
if (wr.success()) {
SG_LOG(SG_NETWORK, SG_DEBUG, "ImageCompressor compressed to " << task.format);
task.stringReadyListener->stringReady(outputStream.str());
}
SG_LOG(SG_NETWORK, SG_DEBUG, "ImageCompressor done for this image" << task.format);
}
}
SG_LOG(SG_NETWORK, SG_DEBUG, "ImageCompressor exiting");
}
void ImageCompressor::addTask(ImageCompressionTask & task)
{
_tasks.push(task);
}
/**
* Based on <a href="http://code.google.com/p/osgworks">osgworks</a> ScreenCapture.cpp
*
*/
class ScreenshotCallback: public osg::Camera::DrawCallback {
public:
ScreenshotCallback()
: _min_delta_tick(1.0/8.0)
{
_previousFrameTick = osg::Timer::instance()->tick();
}
virtual void operator ()(osg::RenderInfo& renderInfo) const
{
osg::Timer_t n = osg::Timer::instance()->tick();
double dt = osg::Timer::instance()->delta_s(_previousFrameTick,n);
if (dt < _min_delta_tick)
return;
_previousFrameTick = n;
bool hasSubscribers = false;
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_lock);
hasSubscribers = !_subscribers.empty();
}
if (hasSubscribers) {
osg::ref_ptr<osg::Image> image = new osg::Image;
const osg::Viewport* vp = renderInfo.getState()->getCurrentViewport();
image->readPixels(vp->x(), vp->y(), vp->width(), vp->height(), GL_RGB, GL_UNSIGNED_BYTE);
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_lock);
while (!_subscribers.empty()) {
try {
_subscribers.back()->imageReady(image);
}
catch (...) {
}
_subscribers.pop_back();
}
}
}
}
void subscribe(ImageReadyListener * subscriber)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_lock);
_subscribers.push_back(subscriber);
}
void unsubscribe(ImageReadyListener * subscriber)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_lock);
_subscribers.remove( subscriber );
}
private:
mutable list<ImageReadyListener*> _subscribers;
mutable OpenThreads::Mutex _lock;
mutable double _previousFrameTick;
double _min_delta_tick;
};
///////////////////////////////////////////////////////////////////////////
class ScreenshotRequest: public ConnectionData, public ImageReadyListener, StringReadyListener {
public:
ScreenshotRequest(const string & window, const string & type, bool stream)
: _type(type), _stream(stream)
{
if ( NULL == osgDB::Registry::instance()->getReaderWriterForExtension(_type))
throw sg_format_exception("Unsupported image type: " + type, type);
osg::Camera * camera = findLastCamera(globals->get_renderer()->getViewerBase(), window);
if ( NULL == camera)
throw sg_error("Can't find a camera for window '" + window + "'");
// add our ScreenshotCallback to the camera
if ( NULL == camera->getFinalDrawCallback()) {
//TODO: are we leaking the Callback on reinit?
camera->setFinalDrawCallback(new ScreenshotCallback());
}
_screenshotCallback = dynamic_cast<ScreenshotCallback*>(camera->getFinalDrawCallback());
if ( NULL == _screenshotCallback)
throw sg_error("Can't find ScreenshotCallback");
requestScreenshot();
}
virtual ~ScreenshotRequest()
{
_screenshotCallback->unsubscribe(this);
}
virtual void imageReady(osg::ref_ptr<osg::Image> rawImage)
{
// called from a rendering thread, not from the main loop
ImageCompressionTask task;
task.image = rawImage;
task.format = _type;
task.stringReadyListener = this;
ImageCompressorSingleton::instance()->addTask(task);
}
void requestScreenshot()
{
_screenshotCallback->subscribe(this);
}
mutable OpenThreads::Mutex _lock;
virtual void stringReady(const string & s)
{
// called from the compressor thread
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_lock);
_compressedData = s;
}
string getScreenshot()
{
string reply;
{
// called from the main loop
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_lock);
reply = _compressedData;
_compressedData.clear();
}
return reply;
}
osg::Camera* findLastCamera(osgViewer::ViewerBase * viewer, const string & windowName)
{
osgViewer::ViewerBase::Windows windows;
viewer->getWindows(windows);
osgViewer::GraphicsWindow* window = NULL;
if (!windowName.empty()) {
for (osgViewer::ViewerBase::Windows::iterator itr = windows.begin(); itr != windows.end(); ++itr) {
if ((*itr)->getTraits()->windowName == windowName) {
window = *itr;
break;
}
}
}
if ( NULL == window) {
if (!windowName.empty()) {
SG_LOG(SG_NETWORK, SG_INFO, "requested window " << windowName << " not found, using first window");
}
window = *windows.begin();
}
SG_LOG(SG_NETWORK, SG_DEBUG, "Looking for last Camera of window '" << window->getTraits()->windowName << "'");
osg::GraphicsContext::Cameras& cameras = window->getCameras();
osg::Camera* lastCamera = 0;
for (osg::GraphicsContext::Cameras::iterator cam_itr = cameras.begin(); cam_itr != cameras.end(); ++cam_itr) {
if (lastCamera) {
if ((*cam_itr)->getRenderOrder() > lastCamera->getRenderOrder()) {
lastCamera = (*cam_itr);
}
if ((*cam_itr)->getRenderOrder() == lastCamera->getRenderOrder()
&& (*cam_itr)->getRenderOrderNum() >= lastCamera->getRenderOrderNum()) {
lastCamera = (*cam_itr);
}
} else {
lastCamera = *cam_itr;
}
}
return lastCamera;
}
bool isStream() const
{
return _stream;
}
const string & getType() const
{
return _type;
}
private:
string _type;
bool _stream;
string _compressedData;
ScreenshotCallback * _screenshotCallback;
};
/**
*/
class CanvasImageRequest : public ConnectionData, public simgear::canvas::CanvasImageReadyListener, StringReadyListener {
public:
ImageCompressionTask *currenttask=NULL;
sc::CanvasPtr canvas;
int connected = 0;
CanvasImageRequest(const string & window, const string & type, int canvasindex, bool stream)
: _type(type), _stream(stream) {
SG_LOG(SG_NETWORK, SG_DEBUG, "CanvasImageRequest:");
if (NULL == osgDB::Registry::instance()->getReaderWriterForExtension(_type))
throw sg_format_exception("Unsupported image type: " + type, type);
CanvasMgr* canvas_mgr = static_cast<CanvasMgr*> (globals->get_subsystem("Canvas"));
if (!canvas_mgr) {
SG_LOG(SG_NETWORK, SG_WARN, "CanvasImage:CanvasMgr not found");
} else {
canvas = canvas_mgr->getCanvas(canvasindex);
if (!canvas) {
throw sg_error("CanvasImage:Canvas not found for index " + std::to_string(canvasindex));
} else {
SG_LOG(SG_NETWORK, SG_DEBUG, "CanvasImage:Canvas found for index " << canvasindex);
//SG_LOG(SG_NETWORK, SG_DEBUG, "CanvasImageRequest: found camera " << camera << ", width=" << canvas->getSizeX() << ", height=%d\n" << canvas->getSizeY());
SGConstPropertyNode_ptr canvasnode = canvas->getProps();
if (canvasnode) {
string canvasname = canvasnode->getStringValue("name");
if (!canvasname.empty()) {
SG_LOG(SG_NETWORK, SG_INFO, "CanvasImageRequest: node=" << canvasnode->getDisplayName().c_str() << ", canvasname =" << canvasname);
}
}
//Looping until success is no option
connected = canvas->subscribe(this);
}
}
}
// Assumption: when unsubscribe returns,there might just be a compressor thread running,
// causing a crash when the deconstructor finishes. Rare, but might happen. Just wait to be sure.
virtual ~CanvasImageRequest() {
if (currenttask){
SG_LOG(SG_NETWORK, SG_ALERT, "CanvasImage: task running, pausing for 15 seconds");
SGTimeStamp::sleepFor(SGTimeStamp::fromSec(15));
}
if (canvas && connected){
canvas->unsubscribe(this);
}
}
virtual void imageReady(osg::ref_ptr<osg::Image> rawImage) {
SG_LOG(SG_NETWORK, SG_DEBUG, "CanvasImage:imageReady");
// called from a rendering thread, not from the main loop
ImageCompressionTask task;
currenttask = &task;
task.image = rawImage;
task.format = _type;
task.stringReadyListener = this;
ImageCompressorSingleton::instance()->addTask(task);
}
void requestCanvasImage() {
connected = canvas->subscribe(this);
}
mutable OpenThreads::Mutex _lock;
virtual void stringReady(const string & s) {
SG_LOG(SG_NETWORK, SG_DEBUG, "CanvasImage:stringReady");
// called from the compressor thread
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_lock);
_compressedData = s;
// allow destructor
currenttask = NULL;
}
string getCanvasImage() {
string reply;
{
// called from the main loop
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_lock);
reply = _compressedData;
_compressedData.clear();
}
return reply;
}
bool isStream() const {
return _stream;
}
const string & getType() const {
return _type;
}
private:
string _type;
bool _stream;
string _compressedData;
};
ScreenshotUriHandler::ScreenshotUriHandler(const std::string& uri)
: URIHandler(uri)
{
}
ScreenshotUriHandler::~ScreenshotUriHandler()
{
ImageCompressorSingleton::instance()->cancel();
//ImageCompressorSingleton::instance()->join();
}
const static string KEY_SCREENSHOT("ScreenshotUriHandler::ScreenshotRequest");
const static string KEY_CANVASIMAGE("ScreenshotUriHandler::CanvasImageRequest");
#define BOUNDARY "--fgfs-screenshot-boundary"
bool ScreenshotUriHandler::handleGetRequest(const HTTPRequest & request, HTTPResponse & response, Connection * connection)
{
if (!ImageCompressorSingleton::instance()->isRunning())
ImageCompressorSingleton::instance()->start();
string type = request.RequestVariables.get("type");
if (type.empty()) type = "jpg";
// string camera = request.RequestVariables.get("camera");
string window = request.RequestVariables.get("window");
bool stream = (!request.RequestVariables.get("stream").empty());
int canvasindex = -1;
string s_canvasindex = request.RequestVariables.get("canvasindex");
if (!s_canvasindex.empty()) canvasindex = atoi(s_canvasindex.c_str());
SGSharedPtr<ScreenshotRequest> screenshotRequest;
SGSharedPtr<CanvasImageRequest> canvasimageRequest;
try {
SG_LOG(SG_NETWORK, SG_DEBUG, "new ScreenshotRequest("<<window<<","<<type<<"," << stream << "," << canvasindex <<")");
if (canvasindex == -1)
screenshotRequest = new ScreenshotRequest(window, type, stream);
else
canvasimageRequest = new CanvasImageRequest(window, type, canvasindex, stream);
}
catch (sg_format_exception & ex)
{
SG_LOG(SG_NETWORK, SG_INFO, ex.getFormattedMessage());
response.Header["Content-Type"] = "text/plain";
response.StatusCode = 410;
response.Content = ex.getFormattedMessage();
return true;
}
catch (sg_error & ex)
{
SG_LOG(SG_NETWORK, SG_INFO, ex.getFormattedMessage());
response.Header["Content-Type"] = "text/plain";
response.StatusCode = 500;
response.Content = ex.getFormattedMessage();
return true;
}
if (!stream) {
response.Header["Content-Type"] = string("image/").append(type);
response.Header["Content-Disposition"] = string("inline; filename=\"fgfs-screen.").append(type).append("\"");
} else {
response.Header["Content-Type"] = string("multipart/x-mixed-replace; boundary=" BOUNDARY);
}
if (canvasindex == -1)
connection->put(KEY_SCREENSHOT, screenshotRequest);
else
connection->put(KEY_CANVASIMAGE, canvasimageRequest);
return false; // call me again thru poll
}
bool ScreenshotUriHandler::poll(Connection * connection)
{
SGSharedPtr<ConnectionData> data = connection->get(KEY_SCREENSHOT);
if (data) {
ScreenshotRequest * screenshotRequest = dynamic_cast<ScreenshotRequest*>(data.get());
if ( NULL == screenshotRequest) return true; // Should not happen, kill the connection
const string & screenshot = screenshotRequest->getScreenshot();
if (screenshot.empty()) {
SG_LOG(SG_NETWORK, SG_DEBUG, "No screenshot available.");
return false; // not ready yet, call again.
}
SG_LOG(SG_NETWORK, SG_DEBUG, "Screenshot is ready, size=" << screenshot.size());
if (screenshotRequest->isStream()) {
std::ostringstream ss;
ss << BOUNDARY << "\r\nContent-Type: image/";
ss << screenshotRequest->getType() << "\r\nContent-Length:";
ss << screenshot.size() << "\r\n\r\n";
connection->write(ss.str().c_str(), ss.str().length());
}
connection->write(screenshot.data(), screenshot.size());
if (screenshotRequest->isStream()) {
screenshotRequest->requestScreenshot();
// continue until user closes connection
return false;
}
// single screenshot, send terminating chunk
connection->remove(KEY_SCREENSHOT);
connection->write("", 0);
return true; // done.
} // Screenshot
// CanvasImage
data = connection->get(KEY_CANVASIMAGE);
CanvasImageRequest * canvasimageRequest = dynamic_cast<CanvasImageRequest*> (data.get());
if (NULL == canvasimageRequest) return true; // Should not happen, kill the connection
if (!canvasimageRequest->connected) {
SG_LOG(SG_NETWORK, SG_INFO, "CanvasImageRequest: not connected. Resubscribing");
canvasimageRequest->requestCanvasImage();
}
const string & canvasimage = canvasimageRequest->getCanvasImage();
if (canvasimage.empty()) {
SG_LOG(SG_NETWORK, SG_INFO, "No canvasimage available.");
return false; // not ready yet, call again.
}
SG_LOG(SG_NETWORK, SG_DEBUG, "CanvasImage is ready, size=" << canvasimage.size());
if (canvasimageRequest->isStream()) {
std::ostringstream ss;
ss << BOUNDARY << "\r\nContent-Type: image/";
ss << canvasimageRequest->getType() << "\r\nContent-Length:";
ss << canvasimage.size() << "\r\n\r\n";
connection->write(ss.str().c_str(), ss.str().length());
}
connection->write(canvasimage.data(), canvasimage.size());
if (canvasimageRequest->isStream()) {
canvasimageRequest->requestCanvasImage();
// continue until user closes connection
return false;
}
// single canvasimage, send terminating chunk
connection->remove(KEY_CANVASIMAGE);
connection->write("", 0);
return true; // done.
}
} // namespace http
} // namespace flightgear

View File

@@ -0,0 +1,40 @@
// ScreenshotUriHandler.hxx -- Provide screenshots via http
//
// Written by Torsten Dreyer, started April 2014.
//
// Copyright (C) 2014 Torsten Dreyer
//
// 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.
#ifndef __FG_SCREENSHOT_URI_HANDLER_HXX
#define __FG_SCREENSHOT_URI_HANDLER_HXX
#include "urihandler.hxx"
namespace flightgear {
namespace http {
class ScreenshotUriHandler : public URIHandler {
public:
ScreenshotUriHandler( const std::string& uri = "/screenshot/" );
~ScreenshotUriHandler();
virtual bool handleGetRequest( const HTTPRequest & request, HTTPResponse & response, Connection * connection );
virtual bool poll( Connection * connection );
};
} // namespace http
} // namespace flightgear
#endif //#define __FG_SCREENSHOT_URI_HANDLER_HXX

View File

@@ -0,0 +1,54 @@
#include "SimpleDOM.hxx"
using std::string;
namespace flightgear {
namespace http {
DOMNode::~DOMNode()
{
for( Children_t::const_iterator it = _children.begin(); it != _children.end(); ++it )
delete *it;
}
string DOMNode::render() const
{
string reply;
reply.append( "<" ).append( _name );
for( Attributes_t::const_iterator it = _attributes.begin(); it != _attributes.end(); ++it ) {
reply.append( " " );
reply.append( it->first );
reply.append( "=\"" );
reply.append( it->second );
reply.append( "\"" );
}
if( _children.empty() ) {
reply.append( " />\r" );
return reply;
} else {
reply.append( ">" );
}
for( Children_t::const_iterator it = _children.begin(); it != _children.end(); ++it ) {
reply.append( (*it)->render() );
}
reply.append( "</" ).append( _name ).append( ">\r" );
return reply;
}
DOMNode * DOMNode::addChild( DOMElement * child )
{
_children.push_back( child );
return dynamic_cast<DOMNode*>(child);
}
DOMNode * DOMNode::setAttribute( const string & name, const string & value )
{
_attributes[name] = value;
return this;
}
}
}

View File

@@ -0,0 +1,64 @@
// SimpleDOM.hxx -- poor man's DOM
//
// Written by Torsten Dreyer
//
// Copyright (C) 2014 Torsten Dreyer
//
// 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.
#ifndef __FG_SIMPLE_DOM_HXX
#define __FG_SIMPLE_DOM_HXX
#include <string>
#include <map>
#include <vector>
namespace flightgear {
namespace http {
class DOMElement {
public:
virtual ~DOMElement() {}
virtual std::string render() const = 0;
};
class DOMTextElement : public DOMElement {
public:
DOMTextElement( const std::string & text ) : _text(text) {}
virtual std::string render() const { return _text; }
private:
std::string _text;
};
class DOMNode : public DOMElement {
public:
DOMNode( const std::string & name ) : _name(name) {}
virtual ~DOMNode();
virtual std::string render() const;
virtual DOMNode * addChild( DOMElement * child );
virtual DOMNode * setAttribute( const std::string & name, const std::string & value );
protected:
std::string _name;
typedef std::vector<const DOMElement*> Children_t;
typedef std::map<std::string,std::string> Attributes_t;
Children_t _children;
Attributes_t _attributes;
};
}
}
#endif // __FG_SIMPLE_DOM_HXX

View File

@@ -0,0 +1,80 @@
// Websocket.cxx -- a base class for websockets
//
// Written by Torsten Dreyer, started April 2014.
//
// Copyright (C) 2014 Torsten Dreyer
//
// 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.
#ifndef WEBSOCKET_HXX_
#define WEBSOCKET_HXX_
#include "HTTPRequest.hxx"
#include <string>
namespace flightgear {
namespace http {
class WebsocketWriter {
public:
virtual ~WebsocketWriter()
{
}
virtual int writeToWebsocket(int opcode, const char * data, size_t len) = 0;
// ref: http://tools.ietf.org/html/rfc6455#section-5.2
int writeContinuation(const char * data, size_t len); // { return writeToWebsocket( 0, data, len ); }
int writeText(const char * data, size_t len)
{
return writeToWebsocket(1, data, len);
}
inline int writeText(const std::string & text)
{
return writeText(text.c_str(), text.length());
}
inline int writeBinary(const char * data, size_t len)
{
return writeToWebsocket(2, data, len);
}
inline int writeConnectionClose(const char * data, size_t len)
{
return writeToWebsocket(8, data, len);
}
inline int writePing(const char * data, size_t len)
{
return writeToWebsocket(9, data, len);
}
inline int writePong(const char * data, size_t len)
{
return writeToWebsocket(0xa, data, len);
}
};
class Websocket {
public:
virtual ~Websocket()
{
}
virtual void close() = 0;
virtual void handleRequest(const HTTPRequest & request, WebsocketWriter & writer) = 0;
virtual void poll(WebsocketWriter & writer) = 0;
};
} // namespace http
} // namespace flightgear
#endif /* WEBSOCKET_HXX_ */

Some files were not shown because too many files have changed in this diff Show More