Qt Virtual Chart Table (QVCT)
CDeviceTcpSbs1.cpp
Go to the documentation of this file.
1 // INDENTING (emacs/vi): -*- mode:c++; tab-width:2; c-basic-offset:2; intent-tabs-mode:nil; -*- ex: set tabstop=2 expandtab:
2 
3 /*
4  * Qt Virtual Chart Table (QVCT)
5  * Copyright (C) 2012 Cedric Dufour <http://cedric.dufour.name>
6  * Author: Cedric Dufour <http://cedric.dufour.name>
7  *
8  * The Qt Virtual Chart Table (QVCT) is free software:
9  * you can redistribute it and/or modify it under the terms of the GNU General
10  * Public License as published by the Free Software Foundation, Version 3.
11  *
12  * The Qt Virtual Chart Table (QVCT) is distributed in the hope
13  * that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
14  * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
15  *
16  * See the GNU General Public License for more details.
17  */
18 
19 // C/C++
20 #include <cmath>
21 
22 // QT
23 #include <QAbstractSocket> // QtNetwork module
24 #include <QDateTime>
25 #include <QDomElement> // QtXml module
26 #include <QHash>
27 #include <QList>
28 #include <QRegExp>
29 #include <QString>
30 #include <QTcpSocket> // QtNetwork module
31 #include <QXmlStreamWriter>
32 
33 // QVCT
34 #include "QVCTRuntime.hpp"
39 
40 
41 //------------------------------------------------------------------------------
42 // CONSTRUCTORS / DESTRUCTOR
43 //------------------------------------------------------------------------------
44 
45 CDeviceTcpSbs1::CDeviceTcpSbs1( const QString& _rqsName )
46  : CDevice( _rqsName )
47  , qsHost( "127.0.0.1" )
48  , iPort( 30003 )
49  , eTimeZone( CUnitTimeZone::LOCAL )
50  , bCallsignLookup( true )
51  , bGroundTraffic( false )
52  , pqTcpSocket( 0 )
53  , bStarted( false )
54  , bPaused( false )
55  , fdCallsignCleanupTimestamp( 0 )
56 {
57  pqTcpSocket = new QTcpSocket( this );
58  QObject::connect( pqTcpSocket, SIGNAL( connected() ), this, SLOT( slotTcpConnected() ) );
59  QObject::connect( pqTcpSocket, SIGNAL( error( QAbstractSocket::SocketError ) ), this, SLOT( slotTcpError( QAbstractSocket::SocketError ) ) );
60  QObject::connect( pqTcpSocket, SIGNAL( readyRead() ), this, SLOT( slotProcessData() ) );
61 }
62 
64 {
65  stop();
66 }
67 
68 
69 //------------------------------------------------------------------------------
70 // METHODS: CDevice (implement/override)
71 //------------------------------------------------------------------------------
72 
74 {
75  QVCT::EStatus __eStatus = QVCT::CRITICAL;
76  switch( _eOperatingMode )
77  {
78 
79  case CDevice::STOP:
80  __eStatus = stop();
81  break;
82 
83  case CDevice::PAUSE:
84  __eStatus = pause();
85  break;
86 
87  case CDevice::START:
88  __eStatus = start();
89  break;
90 
91  default:; // WHAT THE F*** !?!
92  }
93  if( __eStatus != QVCT::OK )
94  {
95  qCritical( "ERROR[%s]: Failed to switch operating mode; host=%s, port=%d", Q_FUNC_INFO, qPrintable( qsHost ), iPort );
96  }
97  return __eStatus;
98 }
99 
101 {
106 }
107 
109 {
110  CDeviceTcpSbs1EditView* __poDeviceTcpSbs1EditView = new CDeviceTcpSbs1EditView( this );
111  if( __poDeviceTcpSbs1EditView->exec() == QDialog::Accepted ) showDetail();
112  delete __poDeviceTcpSbs1EditView;
113 }
114 
115 void CDeviceTcpSbs1::parseQVCT( const QDomElement& _rqDomElement )
116 {
117  QDomElement __qDomElement = _rqDomElement.firstChildElement( "Configuration" );
118  if( __qDomElement.isNull() ) return;
119  qsHost = __qDomElement.attribute( "host", "127.0.0.1" );
120  iPort = __qDomElement.attribute( "port", "30003" ).toInt();
121  eTimeZone = CUnitTimeZone::fromCode( __qDomElement.attribute( "time_zone", "local" ) );
122  bCallsignLookup = (bool)__qDomElement.attribute( "callsign_lookup", "1" ).toInt();
123  bGroundTraffic = (bool)__qDomElement.attribute( "ground_traffic", "0" ).toInt();
124 }
125 
126 void CDeviceTcpSbs1::dumpQVCT( QXmlStreamWriter & _rqXmlStreamWriter ) const
127 {
128  // Device
129  _rqXmlStreamWriter.writeStartElement( "Device" );
130  // ... driver
131  _rqXmlStreamWriter.writeAttribute( "name", qsName );
132  _rqXmlStreamWriter.writeAttribute( "driver", "tcp_sbs1" );
133  // ... configuration
134  _rqXmlStreamWriter.writeStartElement( "Configuration" );
135  _rqXmlStreamWriter.writeAttribute( "host", qsHost );
136  _rqXmlStreamWriter.writeAttribute( "port", QString::number( iPort ) );
137  _rqXmlStreamWriter.writeAttribute( "time_zone", CUnitTimeZone::toCode( eTimeZone ) );
138  _rqXmlStreamWriter.writeAttribute( "callsign_lookup", QString::number( (int)bCallsignLookup ) );
139  _rqXmlStreamWriter.writeAttribute( "ground_traffic", QString::number( (int)bGroundTraffic ) );
140  _rqXmlStreamWriter.writeEndElement(); // Configuration
141  // ... [end]
142  _rqXmlStreamWriter.writeEndElement(); // Device
143 }
144 
145 
146 //------------------------------------------------------------------------------
147 // METHODS
148 //------------------------------------------------------------------------------
149 
150 //
151 // SLOTS
152 //
153 
155 {
156  qDebug( "DEBUG[%s]: Device successfully started", Q_FUNC_INFO );
157  bStarted = true;
159 }
160 
161 
162 void CDeviceTcpSbs1::slotTcpError( QAbstractSocket::SocketError _qSocketError )
163 {
164  QString __qsError = pqTcpSocket->errorString();
165  qCritical( "ERROR[%s]: Connection error; %s", Q_FUNC_INFO, qPrintable( __qsError ) );
166  emit signalError( QString( tr("Connection error")+"; %1" ).arg( __qsError ) );
167  stop();
168 }
169 
171 {
172  // NOTE: SBS-1 protocol: http://www.homepages.mcb.net/bones/SBS/Article/Barebones42_Socket_Data.htm
173  // NOTE: as per QT documentation: "[...] within a slot connected to the readyRead() signal, readyRead() will not be reemitted"
174 
175  static const QRegExp __qRegExpYMD( "^(197\\d|19[89]\\d|[2-9]\\d\\d\\d)/(0?[1-9]|1[12])/(0?[1-9]|[12]\\d|3[01])$" );
176  static const QRegExp __qRegExpHMS( "^(0?\\d|1\\d|2[0-3]):(0?\\d|[1-5]\\d):(0?\\d|[1-5]\\d(\\.\\d+)?)$" );
177 
178  //qDebug( "DEBUG[%s]: Begin", Q_FUNC_INFO );
179  QMutex* __pqMutexDataChange = QVCTRuntime::useMutexDataChange();
180  __pqMutexDataChange->lock();
181 
182  do // data-processing loop
183  {
184 
185  // Check data availability
186  if( !pqTcpSocket->canReadLine() ) break;
187  //qDebug( "DEBUG[%s]: SBS-1 data are waiting to be read", Q_FUNC_INFO );
188 
189  // Retrieve data
190  QString __qsDataLine = QString::fromLatin1( pqTcpSocket->readLine().trimmed() );
191  //qDebug( "DEBUG[%s]: SBS-1 data successfully read; data=%s", Q_FUNC_INFO, qPrintable( __qsDataLine ) );
192 
193  // Do not process data if paused
194  if( bPaused ) continue;
195 
196  // Check data status
197  if( __qsDataLine.isEmpty() ) continue; // We need a non-empty string
198  emit signalActivity();
199 
200  // Check SBS-1 data status
201  int __iMSG;
202  QStringList __qDataFields = __qsDataLine.split( "," );
203  if( __qDataFields.at( 0 ) != "MSG" ) continue; // We process only SBS-1's "MSG" message type
204  if( __qDataFields.size() != 22 ) continue; // "MSG" message type ought to be 22-fields long
205  if( ( __iMSG = __qDataFields.at( 1 ).toInt() ) > 4 ) continue; // Transmission types greater than "4" are useless to us
206  if( __qDataFields.at( 21 ).toInt() < 0 && !bGroundTraffic ) continue; // Ignore ground traffic
207  if( __qDataFields.at( 4 ).isEmpty() ) continue; // We need a valid "HexIdent"
208  QString __qsSource = __qDataFields.at( 4 );
209  //qDebug( "DEBUG[%s]: SBS-1 data are available from source %s", Q_FUNC_INFO, qPrintable( __qsSource ) );
210 
211  // Callsign lookup
212  if( bCallsignLookup )
213  {
214  QString __qsCallsign;
215  double __fdTimestamp = microtime();
216 
217  // Dictionary cleanup (every 300 seconds)
218  if( __fdTimestamp - fdCallsignCleanupTimestamp > (double)300.0 )
219  {
220  fdCallsignCleanupTimestamp = __fdTimestamp;
221  QList<QString> __qListSource = qHashCallsign.keys();
222  for( QList<QString>::const_iterator __iqsSource = __qListSource.begin();
223  __iqsSource != __qListSource.end();
224  ++__iqsSource )
225  // Cleanup stale entries (older than 900 seconds)
226  if( __fdTimestamp - qHashCallsign[ *__iqsSource ].fdTimestamp > (double)900.0 )
227  qHashCallsign.remove( *__iqsSource );
228  }
229 
230  // Actual lookup
231  if( !__qDataFields.at( 10 ).isEmpty() )
232  {
233  __qsCallsign = __qDataFields.at( 10 ).trimmed();
234  if( qHashCallsign.contains( __qsSource ) )
235  qHashCallsign[ __qsSource ].update( __qsCallsign, __fdTimestamp );
236  else
237  qHashCallsign.insert( __qsSource, CCallsign( __qsCallsign, __fdTimestamp ) );
238  }
239  else
240  {
241  if( !qHashCallsign.contains( __qsSource ) ) continue;
242  __qsCallsign = qHashCallsign[ __qsSource ].get( __fdTimestamp );
243  }
244 
245  // Use looked-up callsign
246  __qsSource = __qsCallsign;
247  }
248  if( __iMSG == 1 ) continue;
249 
250  // Parse SBS-1 data
251  CDeviceDataFix __oDeviceDataFix( __qsSource );
252  __oDeviceDataFix.setSourceType( CDeviceDataSource::SBS );
253  __oDeviceDataFix.setCourseFromPosition( false );
254  bool __bDataAvailable = false;
255 
256  // ... time
257  if( !__qDataFields.at( 6 ).isEmpty() && !__qDataFields.at( 7 ).isEmpty()
258  && __qRegExpYMD.exactMatch( __qDataFields.at( 6 ) ) && __qRegExpHMS.exactMatch( __qDataFields.at( 7 ) ) )
259  {
260  QDateTime __qDateTime;
261  __qDateTime.setTimeSpec( eTimeZone == CUnitTimeZone::UTC ? Qt::UTC : Qt::LocalTime );
262  __qDateTime.setDate( QDate( __qRegExpYMD.cap(1).toInt(), __qRegExpYMD.cap(2).toInt(), __qRegExpYMD.cap(3).toInt() ) );
263  __qDateTime.setTime( QTime( __qRegExpHMS.cap(1).toInt(), __qRegExpHMS.cap(2).toInt(), __qRegExpHMS.cap(3).toInt(), !__qRegExpHMS.cap(4).isEmpty() ? 1000*__qRegExpHMS.cap(4).toDouble() : 0 ) );
264  __oDeviceDataFix.setTime( (double)__qDateTime.toTime_t() );
265  }
266  else
267  {
268  // Let's default to use current system time (some SBS-1 devices do not set the date/time fields)
269  __oDeviceDataFix.setTime( (double)QDateTime::currentDateTime().toTime_t() );
270  }
271 
272  // ... position / elevation
273  // SBS-1 elevation: feet
274  if( !__qDataFields.at( 14 ).isEmpty() && !__qDataFields.at( 15 ).isEmpty() )
275  {
276  if( !__qDataFields.at( 11 ).isEmpty() )
277  {
278  __oDeviceDataFix.setPosition( __qDataFields.at( 15 ).toDouble(), __qDataFields.at( 14 ).toDouble(), __qDataFields.at( 11 ).toDouble() * 0.3048 );
279  __oDeviceDataFix.setType( CDeviceDataFix::FIX_3D );
280  }
281  else
282  {
283  __oDeviceDataFix.setPosition( __qDataFields.at( 15 ).toDouble(), __qDataFields.at( 14 ).toDouble() );
284  __oDeviceDataFix.setType( CDeviceDataFix::FIX_2D );
285  }
286  __bDataAvailable = true;
287  }
288 
289  // ... course
290  if( !__qDataFields.at( 13 ).isEmpty() )
291  {
292  __oDeviceDataFix.setBearing( __qDataFields.at( 13 ).toDouble() );
293  __bDataAvailable = true;
294  }
295 
296  // ... speed
297  // SBS-1 (horizontal) speed: knots
298  // SBS-1 vertical speed: feet/minute
299  if( !__qDataFields.at( 12 ).isEmpty() )
300  {
301  if( !__qDataFields.at( 16 ).isEmpty() )
302  {
303  __oDeviceDataFix.setSpeed( __qDataFields.at( 12 ).toDouble() / 1.94384449244, __qDataFields.at( 16 ).toDouble() / 196.8503937 );
304  }
305  else
306  {
307  __oDeviceDataFix.setSpeed( __qDataFields.at( 12 ).toDouble() / 1.94384449244 );
308  }
309  __bDataAvailable = true;
310  }
311 
312  // ... hexident/callsign
313  if( bCallsignLookup || !__qDataFields.at( 10 ).isEmpty() )
314  {
315  __oDeviceDataFix.setText( bCallsignLookup ? "HEX:"+__qDataFields.at( 4 ) : "C/S:"+__qDataFields.at( 10 ) );
316  __bDataAvailable = true;
317  }
318 
319  // [end]
320  if( __bDataAvailable )
321  {
322  emit signalDataFix( __oDeviceDataFix );
323  }
324 
325  }
326  while( true ); // data-processing loop
327 
328  __pqMutexDataChange->unlock();
329  //qDebug( "DEBUG[%s]: End", Q_FUNC_INFO );
330 }
331 
332 //
333 // OTHER
334 //
335 
337 {
338  bPaused = false;
339  if( bStarted )
340  {
341  pqTcpSocket->abort();
342  qHashCallsign.clear();
343  bStarted = false;
344  }
345  qDebug( "DEBUG[%s]: Device successfully stopped", Q_FUNC_INFO );
347  return QVCT::OK;
348 }
349 
351 {
352  if( bStarted )
353  {
354  bPaused = !bPaused;
355  }
356  qDebug( "DEBUG[%s]: Device successfully paused", Q_FUNC_INFO );
358  return QVCT::OK;
359 }
360 
362 {
363  if( bStarted ) return QVCT::OK;
364  bPaused = false;
366  pqTcpSocket->connectToHost( qsHost, iPort, QIODevice::ReadOnly );
367  // NOTE: device is further "started" once the 'connected' signal is received
368  return QVCT::OK;
369 }
370 
372 {
373  if( !bStarted ) return CDevice::STOP;
374  if( bPaused ) return CDevice::PAUSE;
375  return CDevice::START;
376 }
void setBearing(double _fdBearing)
Sets this course's bearing, in degrees.
Definition: CDataCourse.hpp:96
void setSpeed(double _fdSpeed, double _fdSpeedVertical=UNDEFINED_SPEED)
Sets this course's horizontal speed, in meters per second.
void setPosition(double _fdLongitude, double _fdLatitude, double _fdElevation=UNDEFINED_ELEVATION)
Sets new coordinates.
void setTime(double _fdTime)
Sets the time, in seconds from Unix epoch.
Definition: CDataTime.cpp:60
Fix data [source,time,position,course,DOPs,...].
void setText(const QString &_rqsText)
Sets the additional textual data string.
void setType(int _eType)
Sets the fix type.
void setCourseFromPosition(bool _bCourseFromPosition)
Sets whether to use position to compute course.
void setSourceType(EType _eType)
Sets the source type.
virtual void refreshContent()
Refreshes the content of the underlying widget.
[UI] Route container's edit view
Callsign dictionary entry.
QTcpSocket * pqTcpSocket
TCP socket.
virtual void showDetail()
Displays the device's details (in the appropriate widget/view)
void parseQVCT(const QDomElement &_rqDomElement)
Retrieves the device's configuration from the given QVCT source (file)
double fdCallsignCleanupTimestamp
Callsign dictionary cleanup timestamp.
CUnitTimeZone::EUnit eTimeZone
Time zone.
bool bPaused
Pause status.
bool bStarted
Start status.
virtual void showEdit()
Displays the device's edit (configuration) widget/view.
QString qsHost
Network host.
QVCT::EStatus pause()
Pause the device.
void slotProcessData()
Slots to process device data.
virtual ~CDeviceTcpSbs1()
CDeviceTcpSbs1(const QString &_rqsName)
void slotTcpConnected()
Slots to report TCP connection.
void slotTcpError(QAbstractSocket::SocketError _qSocketError)
Slots to report TCP error.
QHash< QString, CCallsign > qHashCallsign
Callsign dictionary.
bool bGroundTraffic
Ground traffic (inclusion/track)
int iPort
Network port.
QVCT::EStatus start()
Stop the device.
bool bCallsignLookup
Callsign lookup.
QVCT::EStatus setOperatingMode(CDevice::EOperatingMode _eOperatingMode)
Sets the device's operating mode.
QVCT::EStatus stop()
Start the device.
void dumpQVCT(QXmlStreamWriter &_rqXmlStreamWriter) const
Stores the device's configuration to the given QVCT destination (file)
CDevice::EOperatingMode status()
Returns the device's status (operating mode)
Generic navigation device (GPS, speedometer, compass, etc.)
Definition: CDevice.hpp:43
void signalDataFix(const CDeviceDataFix &_roDeviceDataFix)
Signal emitted by the device when an updated fix is available.
void signalActivity()
Signal emitted by the device when activity occures.
EOperatingMode
Device operating mode (stop, start, pause)
Definition: CDevice.hpp:58
@ PAUSE
Definition: CDevice.hpp:58
@ STOP
Definition: CDevice.hpp:58
@ START
Definition: CDevice.hpp:58
void signalError(const QString &_rqsErrorMessage)
Signal emitted by the device when an error occured.
void signalOperatingMode(CDevice::EOperatingMode _eOperatingMode)
Signal emitted by the device when its operating mode changed.
void switchView(EView eView)
Displays the requested container/item details (switching to the appropriate widget)
@ DEVICE
Device overlay.
void switchView(EView eView)
Displays the requested overlay (switching to the appropriate tab)
void setOverlayObject(COverlayObject *_poOverlayObject)
Sets the overlay object to be displayed (and refreshes the underlying widget)
QString qsName
Object name.
Time zone (UTC, local) selection class.
static QString toCode(EUnit _eUnit)
Returns the machine-friendly code corresponding to the given format/unit ID.
static EUnit fromCode(const QString &_rqsCode)
Returns the format/unit ID corresponding to the given machine-friendly code.
@ UTC
Universal Time Coordinates.
static COverlayDetailView * useOverlayDetailView()
static CDeviceDetailView * useDeviceDetailView()
static QMutex * useMutexDataChange()
static COverlayListView * useOverlayListView()
EStatus
Definition: QVCT.hpp:41
@ OK
Definition: QVCT.hpp:41
@ CRITICAL
Definition: QVCT.hpp:41
double microtime()
Returns the system time with microseconds resolution, in seconds.
Definition: main.cpp:30