2019-03-19 21:09:03 +00:00
using System ;
using System.Collections.Generic ;
using System.Collections ;
using System.Linq ;
using System.Text ;
using System.Threading ;
using System.ComponentModel ;
using System.Windows ;
using System.Globalization ;
using System.Net ;
using System.Net.Sockets ;
using System.IO ;
2019-03-21 11:25:33 +00:00
using AirScout.Core ;
2019-03-19 21:09:03 +00:00
using AirScout.Aircrafts ;
using AirScout.PlaneFeeds.Generic ;
using LibADSB ;
using ScoutBase.Core ;
namespace AirScout.PlaneFeeds
{
public class ADSBMessage
{
public string RawMessage = "" ;
public DateTime TimeStamp = DateTime . UtcNow ;
public int SignalStrength = 0 ;
}
public class PlaneFeedSettings_ADSB
{
[Browsable(true)]
[DescriptionAttribute("Server address for raw ADS-B data.\nUse localhost for running on the same machine.")]
public virtual string Server
{
get
{
return Properties . Settings . Default . ADSB_Server ;
}
set
{
Properties . Settings . Default . ADSB_Server = value ;
Properties . Settings . Default . Save ( ) ;
}
}
[Browsable(true)]
[DescriptionAttribute("Server port for raw ADS-B data.\nADSBSharp.exe: Port 47806\nRTL1090: Port 31001")]
public virtual int Port
{
get
{
return Properties . Settings . Default . ADSB_Port ;
}
set
{
Properties . Settings . Default . ADSB_Port = value ;
Properties . Settings . Default . Save ( ) ;
}
}
[Browsable(true)]
[DescriptionAttribute("Interval for updating ADS-B data [sec] . ")]
public virtual int Interval
{
get
{
return Properties . Settings . Default . ADSB_Interval ;
}
set
{
Properties . Settings . Default . ADSB_Interval = value ;
Properties . Settings . Default . Save ( ) ;
}
}
[Browsable(true)]
[DescriptionAttribute("Use binary data format for ADS-B data.\nTrue: Use binary format (ADS Beast with MLAT)\nFalse: Use ASCII format (AVR with/without MLAT)")]
public virtual bool Binary
{
get
{
return Properties . Settings . Default . ADSB_Binary ;
}
set
{
Properties . Settings . Default . ADSB_Binary = value ;
Properties . Settings . Default . Save ( ) ;
}
}
[Browsable(true)]
[DescriptionAttribute("Report ADS-B messages and show in status line.")]
public virtual bool ReportMessages
{
get
{
return Properties . Settings . Default . ADSB_Report_Messages ;
}
set
{
Properties . Settings . Default . ADSB_Report_Messages = value ;
Properties . Settings . Default . Save ( ) ;
}
}
[Browsable(true)]
[DescriptionAttribute("Marks locally received aircrafts by adding '@' to the call sign")]
public virtual bool MarkLocal
{
get
{
return Properties . Settings . Default . ADSB_MarkLocal ;
}
set
{
Properties . Settings . Default . ADSB_MarkLocal = value ;
Properties . Settings . Default . Save ( ) ;
}
}
}
public class PlaneFeed_ADSB : PlaneFeed
{
[Browsable(false)]
public override string Name
{
get
{
return Properties . Settings . Default . ADSB_Name ; ;
}
protected set
{
Properties . Settings . Default . ADSB_Name = value ;
Properties . Settings . Default . Save ( ) ;
}
}
[Browsable(false)]
public override string Disclaimer
{
get
{
return Properties . Settings . Default . ADSB_Disclaimer ;
}
protected set
{
Properties . Settings . Default . ADSB_Disclaimer = value ;
Properties . Settings . Default . Save ( ) ;
}
}
[Browsable(false)]
public override string DisclaimerAccepted
{
get
{
return Properties . Settings . Default . ADSB_Disclaimer_Accepted ;
}
set
{
Properties . Settings . Default . ADSB_Disclaimer_Accepted = value ;
Properties . Settings . Default . Save ( ) ;
}
}
[Browsable(false)]
public override string Info
{
get
{
return Properties . Settings . Default . ADSB_Info ;
}
protected set
{
Properties . Settings . Default . ADSB_Info = value ;
Properties . Settings . Default . Save ( ) ;
}
}
public new PlaneFeedSettings_ADSB FeedSettings = new PlaneFeedSettings_ADSB ( ) ;
public PlaneFeed_ADSB ( )
: base ( )
{
HasSettings = true ;
}
private ADSBMessage ReceiveBinaryMsg ( Stream stream )
{
// read Mode-S beast binary input
string ADSB = null ;
int signal_strength = 0 ;
long nanosec = 0 ;
long daysec = 0 ;
DateTime timestamp = DateTime . UtcNow ;
byte [ ] buffer = new byte [ 23 ] ;
// wait for escape character
DateTime start = DateTime . UtcNow ;
DateTime stop = DateTime . Now ;
do
{
stream . Read ( buffer , 0 , 1 ) ;
// System.Console.WriteLine(BitConverter.ToString(buffer,0,1));
if ( buffer [ 0 ] = = 0x1A )
{
// read next character
stream . Read ( buffer , 1 , 1 ) ;
switch ( buffer [ 1 ] )
{
case 0x31 :
// do not decode
ADSB = null ;
break ;
case 0x32 :
// 7 byte short frame
// read timestamp
stream . Read ( buffer , 2 , 6 ) ;
nanosec = ( ( buffer [ 4 ] & 0x3f ) < < 24 ) |
( buffer [ 5 ] < < 16 ) |
( buffer [ 6 ] < < 8 ) |
( buffer [ 7 ] ) ;
daysec = ( buffer [ 2 ] < < 10 ) |
( buffer [ 3 ] < < 2 ) |
( buffer [ 4 ] > > 6 ) ;
timestamp = DateTime . Today . AddSeconds ( daysec ) ;
timestamp = timestamp . AddMilliseconds ( nanosec / 1000 ) ;
// plausibility check
if ( Math . Abs ( ( DateTime . Now - timestamp ) . Seconds ) > 10 )
{
// time difference > 10sec --> discard timestamp
timestamp = DateTime . UtcNow ;
}
// read signal strength
stream . Read ( buffer , 8 , 1 ) ;
// plausibility check
if ( Math . Abs ( ( DateTime . Now - timestamp ) . Seconds ) > 10 )
{
// time difference > 10sec --> discard timestamp
timestamp = DateTime . UtcNow ;
}
signal_strength = buffer [ 8 ] ;
// read frame
stream . Read ( buffer , 9 , 7 ) ;
// convert to AVR string
ADSB = BitConverter . ToString ( ( byte [ ] ) buffer , 9 , 7 ) . Replace ( "-" , String . Empty ) ;
ADSB = "*" + ADSB + ";" ;
break ;
case 0x33 :
// 14 byte long frame
// read timestamp
stream . Read ( buffer , 2 , 6 ) ;
nanosec = ( ( buffer [ 4 ] & 0x3f ) < < 24 ) |
( buffer [ 5 ] < < 16 ) |
( buffer [ 6 ] < < 8 ) |
( buffer [ 7 ] ) ;
daysec = ( buffer [ 2 ] < < 10 ) |
( buffer [ 3 ] < < 2 ) |
( buffer [ 4 ] > > 6 ) ;
timestamp = DateTime . Today . AddSeconds ( daysec ) ;
timestamp = timestamp . AddMilliseconds ( nanosec / 1000 ) ;
// plausibility check
if ( Math . Abs ( ( DateTime . Now - timestamp ) . Seconds ) > 10 )
{
// time difference > 10sec --> discard timestamp
timestamp = DateTime . UtcNow ;
}
// read signal strength
stream . Read ( buffer , 8 , 1 ) ;
signal_strength = buffer [ 8 ] ;
// read frame
stream . Read ( buffer , 9 , 14 ) ;
// convert to AVR string
ADSB = BitConverter . ToString ( ( byte [ ] ) buffer , 9 , 14 ) . Replace ( "-" , String . Empty ) ;
ADSB = "*" + ADSB + ";" ;
break ;
default :
// false decode
ADSB = null ;
break ;
}
}
// check for timeout 10sec
stop = DateTime . UtcNow ;
if ( stop - start > new TimeSpan ( 0 , 0 , 10 ) )
throw new TimeoutException ( ) ;
}
while ( ( ADSB = = null ) & & ! this . CancellationPending ) ;
if ( ADSB = = null )
return null ;
ADSBMessage msg = new ADSBMessage ( ) ;
msg . RawMessage = ADSB ;
msg . TimeStamp = timestamp ;
msg . SignalStrength = signal_strength ;
return msg ;
}
private ADSBMessage ReceiveAVRMsg ( StreamReader sr )
{
ADSBMessage msg = new ADSBMessage ( ) ;
// read AVR format input
msg . RawMessage = sr . ReadLine ( ) ;
if ( msg . RawMessage . StartsWith ( "*" ) )
{
// standard AVR message
// no timestamp in telegram --> set timestamp after reading
msg . TimeStamp = DateTime . UtcNow ;
msg . SignalStrength = 0 ;
}
else if ( msg . RawMessage . StartsWith ( "@" ) )
{
// extended AVR message wit MLAT
// convert into standard message format
// extract time string
string time = msg . RawMessage . Substring ( 1 , 12 ) ;
// TODO: interprete the MLAT timestamp!
msg . TimeStamp = DateTime . UtcNow ;
msg . RawMessage = "*" + msg . RawMessage . Remove ( 0 , 13 ) ;
}
return msg ;
}
protected override void OnDoWork ( DoWorkEventArgs e )
{
Log . WriteMessage ( "Started." ) ;
PlaneFeedWorkEventArgs args = ( PlaneFeedWorkEventArgs ) e . Argument ;
// set directories
AppDirectory = args . AppDirectory ;
AppDataDirectory = args . AppDataDirectory ;
LogDirectory = args . LogDirectory ;
TmpDirectory = args . TmpDirectory ;
DatabaseDirectory = args . DatabaseDirectory ;
// set boundaries from arguments
MaxLat = args . MaxLat ;
MinLon = args . MinLon ;
MinLat = args . MinLat ;
MaxLon = args . MaxLon ;
MyLat = args . MyLat ;
MyLon = args . MyLon ;
DXLat = args . DXLat ;
DXLon = args . DXLon ;
MinAlt = args . MinAlt ;
MaxAlt = args . MaxAlt ;
2019-03-21 11:25:33 +00:00
// keep history settings from arguments
KeepHistory = args . KeepHistory ;
2019-03-19 21:09:03 +00:00
// check boundaries
if ( ( MaxLat < = MinLat ) | | ( MaxLon < = MinLon ) )
{
Status = STATUS . ERROR ;
this . ReportProgress ( ( int ) PROGRESS . ERROR , "Area boundaries mismatch. Check your Covered Area parameters!" ) ;
}
else
{
Thread . CurrentThread . Priority = ThreadPriority . Highest ;
Status = STATUS . OK ;
ADSBDecoder decoder = new ADSBDecoder ( ) ;
DateTime lastreported = DateTime . UtcNow ;
StreamReader sr = null ;
TcpClient client = null ;
ADSBMessage msg = null ;
// outer loop
do
{
try
{
// setup TCP listener
client = new TcpClient ( ) ;
client . Connect ( Properties . Settings . Default . ADSB_Server , Properties . Settings . Default . ADSB_Port ) ;
sr = new StreamReader ( client . GetStream ( ) ) ;
// inner loop
// receive messages in a loop
do
{
if ( FeedSettings . Binary )
msg = ReceiveBinaryMsg ( sr . BaseStream ) ;
else
msg = ReceiveAVRMsg ( sr ) ;
// decode the message
{
Console . Write ( msg . RawMessage + " --> " ) ;
// try to decode the message
string info = "" ;
try
{
info = decoder . DecodeMessage ( msg . RawMessage , msg . TimeStamp ) ;
// report messages to main window if activated
if ( FeedSettings . ReportMessages & & ( info . StartsWith ( "[" ) ) )
this . ReportProgress ( ( int ) PROGRESS . STATUS , info ) ;
}
catch ( Exception ex )
{
Console . WriteLine ( ex . Message ) ;
}
Console . WriteLine ( info ) ;
}
DateTime stop = DateTime . UtcNow ;
// check if update report is necessary
if ( ( DateTime . UtcNow - lastreported ) . TotalSeconds > FeedSettings . Interval )
{
lastreported = DateTime . UtcNow ;
// time to report planes
ArrayList list = decoder . GetPlanes ( ) ;
if ( list . Count > 0 )
{
// convert to plane info list
List < PlaneInfo > planes = new List < PlaneInfo > ( ) ;
foreach ( ADSBInfo info in list )
{
PlaneInfo planeinfo = new PlaneInfo ( ) ;
planeinfo . Time = info . Timestamp ;
planeinfo . Hex = info . ICAO24 ;
// mark call with "@" if option is enabled
planeinfo . Call = ( FeedSettings . MarkLocal ) ? "@" + info . Call : info . Call ;
planeinfo . Lat = info . Lat ;
planeinfo . Lon = info . Lon ;
planeinfo . Alt = info . Alt ;
planeinfo . Speed = info . Speed ;
planeinfo . Track = info . Heading ;
planeinfo . Reg = "[unknown]" ;
planeinfo . Type = "[unknown]" ;
planeinfo . Manufacturer = "[unknown]" ;
planeinfo . Model = "[unknown]" ;
planeinfo . Category = PLANECATEGORY . NONE ;
// try to get the registration and type
AircraftDesignator aircraft = AircraftData . Database . AircraftFindByHex ( planeinfo . Hex ) ;
if ( aircraft ! = null )
{
planeinfo . Reg = aircraft . Reg ;
planeinfo . Type = aircraft . TypeCode ;
// try to get the type
AircraftTypeDesignator type = AircraftData . Database . AircraftTypeFindByICAO ( planeinfo . Type ) ;
if ( planeinfo ! = null )
{
planeinfo . Manufacturer = type . Manufacturer ;
planeinfo . Model = type . Model ;
planeinfo . Category = type . Category ;
}
}
planes . Add ( planeinfo ) ;
}
ReportProgress ( ( int ) PROGRESS . PLANES , planes ) ;
AircraftData . Database . PlaneInfoBulkInsertOrUpdateIfNewer ( planes ) ;
string message = "[" + lastreported . ToString ( "HH:mm:ss" ) + "] " +
decoder . Count . ToString ( ) + " Positions updated from local ADS-B receiver." ;
this . ReportProgress ( ( int ) PROGRESS . STATUS , message ) ;
}
}
}
while ( ( msg ! = null ) & & ! CancellationPending ) ;
}
catch ( Exception ex )
{
Log . WriteMessage ( ex . Message ) ;
this . ReportProgress ( ( int ) PROGRESS . ERROR , "Error reading from TCP connection: " + ex . Message ) ;
Thread . Sleep ( 10000 ) ;
}
finally
{
// try to close the stream and TCP client
try
{
if ( sr ! = null )
sr . Close ( ) ;
}
catch
{
}
try
{
if ( client ! = null )
client . Close ( ) ;
}
catch
{
}
}
}
while ( ! this . CancellationPending | | ( Status ! = STATUS . OK ) ) ;
}
this . ReportProgress ( ( int ) PROGRESS . FINISHED ) ;
Log . WriteMessage ( "Finished." ) ;
}
public override Object GetFeedSettings ( )
{
return FeedSettings ;
}
}
}