CONTEXT:
I've got someone here (faculty in oceanography dept) with a fairly small (I think) project .. it probably will require a C++ programmer, although maybe any programmer capable of reading some existing open-source C++ source-code and then adapting it in "language of their choice" may also fit the bill.
The project involves, more or less:
-capture of AIS Format NMEA data from serial port, basically navigational data broadcast by a ship every 2-10 seconds which states "Ship Identification", "Ship Location", "Ship Heading", "ship speed" and a few other chunks of data. In theory the program to be developed would start with a raw data stream via rs232 serial port (provided by an attached antenna and decoder/signal receiver blackbox which hooks to the serial port of a computer and thus provides the encoded data stream)
-parsing the data, ignoring any data for a ship already observed and logged in the past minute -any ship not observed in past minute is logged as "present" and its ID and location are stored. (other data is irreleant) -this data is then to be captured regularly so that at the end of it all, there is an easy way to see what ships announced their presence at granularity of ~1-3 minutes.
(By default there will be "too much data", both in terms of granularity - ie - each ship announces itself every 2-10 seconds, and all we care about is every minute; and also of course that most of the data is not of interest either)
(the big-picture context: The researcher in question is involved in monitoring whale-hits by large boats, and incidents of ships-in "no travel zones" that are officially off-limits to boats due to whale-presence, but in practice some boats cheat sometimes and travel through the "blackout zones". However since they all by law have these transponders that announce their location/heading/presence, it provides an easy way to monitor if people are cheating / slipping through the "no fly zones" and putting whales at risk of collision).
The reason C++ comes into it: There is an open-source package discussed at the URL,
http://www.bigdumboat.com./cpnfaq.htm
...which has a project on sourceforge .. which is capable of processing this data and then rendering it graphically in a nice GUI, either on linux or windows. This project is officially beta but the guy who developed it has been using it "in production" on his own boat for navigation for many years, ie, it is pretty solid product.
In particular there is a chunk of code which appears to be dedicated to the capture and processing of the serial port AIS data stream. The software is written in C++ but is seems quite well commented / fairly human readable.
Just because I'm crazy I've pasted below a copy of this chunk of the code (easier than having to go download for quick-glance-purposes)
(The desired target application for my purposes would be a small command-line program that would simply be run on linux, capture data from serial port and dump output to a constantly growing logfile)
(the log would be rotated periodically by the OS, and content would be emailed out from this box to another system where analysis would be done by human using some basic stats software)
So.. the fun questions...
-do you know anyone who might be willing-and-able to help out with a project like this? In theory it would have some kind of contract-salary (plus of course the glam and fame of helping out with a high profile research project :-)
Please contact me if this sounds like it might be up your alley / of any interest.
--Tim Chipman
tim.chipman@dal.ca
---paste---
bash-3.00$ pwd
/home/chipmant/open_cpn/opencpn-1.2.0/src
bash-3.00$ ls -la
total 1528
drwxr-xr-x 6 chipmant chipmant 4096 Apr 20 03:12 .
drwxr-xr-x 5 chipmant chipmant 4096 Apr 20 03:12 ..
-rw-r--r-- 1 chipmant chipmant 8733 Mar 30 19:57 about.cpp
-rw-r--r-- 1 chipmant chipmant 45510 Apr 19 20:59 ais.cpp <======= THIS
IS THE MODULE IN QUESTION
-rw-r--r-- 1 chipmant chipmant 8190 Aug 21 2006 bbox.cpp
drwxr-xr-x 2 chipmant chipmant 4096 Apr 20 03:12 bitmaps
-rw-r--r-- 1 chipmant chipmant 88489 Apr 19 23:06 chart1.cpp
-rw-r--r-- 1 chipmant chipmant 63733 Apr 19 19:22 chartdb.cpp
-rw-r--r-- 1 chipmant chipmant 111010 Apr 19 23:06 chartimg.cpp
-rw-r--r-- 1 chipmant chipmant 133374 Apr 19 19:22 chcanv.cpp
-rw-r--r-- 1 chipmant chipmant 17685 Apr 5 21:52 concanv.cpp
-rw-r--r-- 1 chipmant chipmant 9756 Apr 1 00:40 cutil.c
-rw-r--r-- 1 chipmant chipmant 69571 Apr 16 19:32 georef.c
drwxr-xr-x 2 chipmant chipmant 4096 Apr 20 03:12 mygdal
-rw-r--r-- 1 chipmant chipmant 40511 Apr 19 19:30 mygeom.cpp
drwxr-xr-x 2 chipmant chipmant 4096 Apr 20 03:12 myiso8211
-rw-r--r-- 1 chipmant chipmant 68665 Apr 17 22:33 navutil.cpp
drwxr-xr-x 2 chipmant chipmant 4096 Apr 20 03:12 nmea0183
-rw-r--r-- 1 chipmant chipmant 49513 Apr 19 20:50 nmea.cpp
-rw-r--r-- 1 chipmant chipmant 32592 Apr 16 23:33 ocpn_pixel.cpp
-rw-r--r-- 1 chipmant chipmant 38082 Apr 19 19:43 options.cpp
-rw-r--r-- 1 chipmant chipmant 13599 Mar 30 01:30 routeman.cpp
-rw-r--r-- 1 chipmant chipmant 136287 Apr 1 00:43 s52cnsy.cpp
-rw-r--r-- 1 chipmant chipmant 114764 Apr 16 23:33 s52plib.cpp
-rw-r--r-- 1 chipmant chipmant 10085 Apr 19 19:22 s52utils.cpp
-rw-r--r-- 1 chipmant chipmant 116780 Apr 19 23:02 s57chart.cpp
-rw-r--r-- 1 chipmant chipmant 8276 Apr 19 19:22 sercomm.cpp
-rw-r--r-- 1 chipmant chipmant 14420 Apr 16 19:29 statwin.cpp
-rw-r--r-- 1 chipmant chipmant 82401 Apr 19 23:09 tcmgr.cpp
-rw-r--r-- 1 chipmant chipmant 4739 Apr 16 19:29 thumbwin.cpp
-rw-r--r-- 1 chipmant chipmant 71129 Apr 16 23:33 tri.c
-rw-r--r-- 1 chipmant chipmant 15806 Feb 5 22:08 wificlient.cpp
-rwxr-xr-x 1 chipmant chipmant 50142 Apr 16 19:29 wvschart.cpp
bash-3.00$
bash-3.00$
bash-3.00$ more ais.cpp
/******************************************************************************
* $Id: ais.cpp,v 1.2 2007/02/06 02:04:51 dsr Exp $
*
* Project: OpenCPN
* Purpose: AIS Decoder Object
* Author: David Register
*
***************************************************************************
* Copyright (C) $YEAR$ by $AUTHOR$ *
* $EMAIL$ *
* *
* 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., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************
*
* $Log: ais.cpp,v $
* Revision 1.2 2007/02/06 02:04:51 dsr
* Correct text decode algorithm
*
* Revision 1.1 2006/11/01 02:17:37 dsr
* AIS Support
*
*/
//#include "wx/wxprec.h"
//#ifndef WX_PRECOMP
#include "wx/wx.h"
//#endif //precompiled headers
#include "wx/tokenzr.h"
//#include "wx/datetime.h"
#include <stdlib.h>
#include <math.h>
#include <time.h>
#include "dychart.h"
#include "ais.h"
#include "chart1.h"
#include "nmea.h" // for DNSTestThread
#include "navutil.h" // for Select
extern OCP_AIS_Thread *pAIS_Thread;
extern wxString *pAISDataSource;
extern int s_dns_test_flag;
extern Select *pSelectAIS;
CPL_CVSID("$Id: ais.cpp,v 1.2 2007/02/06 02:04:51 dsr Exp $");
// the first string in this list produces a 6 digit MMSI... BUGBUG
char test_str[24][79] = {
"!AIVDM,1,1,,A,100Rsb@P1PJREovFCnS7n?vt08Ag,0*13**",
"!AIVDM,1,1,,A,15Mnh`PP0jIcl78Csm`hCgvB2D00,0*29**",
"!AIVDM,2,1,1,A,55Mnh`P00001L@7S3GP
=Dl8E8h4pB2222222220P0`A6357d07PT851F,0*75**",
"!AIVDM,2,2,1,A,0Dp0jE6H8888880,2*40**",
"!AIVDM,1,1,,A,15Mnh`PP0rIce3VCr8nPWgvt24B0,0*10**",
"!AIVDM,1,1,,A,15Mnh`PP0pIcfJ<Cs0lPFwwR24Bt,0*6B**",
"!AIVDM,1,1,,A,15Mnh`PP0pIcfKBCs23P6gvB2D00,0*7E**",
"!AIVDM,1,1,,A,15Mnh`PP0nIcfL8Cs3DPLwvt28AV,0*5C**",
"!AIVDM,1,1,,A,15Mnh`PP0mIcfSlCs7w@KOwT2L00,0*4E**",
"!AIVDM,1,1,,A,15Mnh`PP0lIcjQRCsOK1LwvB2D00,0*6A**",
"!AIVDM,1,1,,A,15Mnh`PP0mIcj`NCsPNADOvt2@AQ,0*3E**",
"!AIVDM,1,1,,A,15Mnh`PP0mIcjfhCsQLAHOwT24H0,0*2C**",
"!AIVDM,1,1,,A,15Mnh`PP0mIcjlNCsRCiBwvB2@4s,0*4A**",
"!AIVDM,1,1,,A,15Mnh`PP0nIck5@CsUBi2gvB2<00,0*42**",
"!AIVDM,1,1,,A,15Mnh`PP0nIck:PCsVO0uOvt28AQ,0*47**",
"!AIVDM,1,1,,A,15Mnh`PP0oIck?:CsWQi0?wR2L00,0*19**",
"!AIVDM,1,1,,A,15Mnh`PP0oIckRVCscni3gvB24H@,0*29**",
"!AIVDM,1,1,,A,15Mnh`PP0pIck`VCse0i8wvt20Rg,0*48**",
"!AIVDM,1,1,,A,15Mnh`PP0nIckelCsf0hrgwR2D00,0*57**",
"!AIVDM,1,1,,A,15Mnh`PP0mIckiHCsfn@kOvB20S4,0*39**",
"!AIVDM,1,1,,A,15Mnh`PP0nIckmtCsh<0h?vr2HA6,0*05**",
"!AIVDM,1,1,,A,15Mnh`PP0nIckqdCsiC@iOwR2@LP,0*34**",
"!AIVDM,1,1,,A,15Mnh`PP0nIckttCsjEhdgvB2H4m,0*75**",
"!AIVDM,1,1,,A,15Mnh`PP0mIcl0jCskPhiwvr2D00,0*47**",
};
//-------------------------------------------------------------------------------------------------------------
// OCP_AIS_Thread Static data store
//-------------------------------------------------------------------------------------------------------------
extern char
ais_rx_share_buffer[MAX_RX_MESSSAGE_SIZE];
extern unsigned int ais_rx_share_buffer_length;
extern ENUM_BUFFER_STATE ais_rx_share_buffer_state;
extern wxMutex *ais_ps_mutexProtectingTheRXBuffer;
//---------------------------------------------------------------------------------
//
// AIS_Target_Data Implementation
//
//---------------------------------------------------------------------------------
AIS_Target_Data::AIS_Target_Data()
{
strcpy(&ShipName[0], "UNKNOWN ");
}
//---------------------------------------------------------------------------------
//
// AIS_Decoder Helpers
//
//---------------------------------------------------------------------------------
AIS_Bitstring::AIS_Bitstring(const char *str)
{
byte_length = strlen(str);
for(int i=0 ; i<byte_length ; i++)
{
bitbytes[i] = to_6bit(str[i]);
}
}
// Convert printable characters to IEC 6 bit representation
// according to rules in IEC AIS Specification
unsigned char AIS_Bitstring::to_6bit(const char c)
{
if(c < 0x30)
return (unsigned char)-1;
if(c > 0x77)
return (unsigned char)-1;
if((0x57 < c) && (c < 0x60))
return (unsigned char)-1;
unsigned char cp = c;
cp += 0x28;
if(cp > 0x80)
cp += 0x20;
else
cp += 0x28;
return (unsigned char)(cp & 0x3f);
}
int AIS_Bitstring::GetInt(int sp, int len)
{
int acc = 0;
int s0p = sp-1; // to zero base
int cp, cx, c0, cs;
for(int i=0 ; i<len ; i++)
{
acc = acc << 1;
cp = (s0p + i) / 6;
cx = bitbytes[cp];
cs = 5 - ((s0p + i) % 6);
c0 = (cx >> (5 - ((s0p + i) % 6))) & 1;
acc |= c0;
}
return acc;
}
bool AIS_Bitstring::GetStr(int sp, int len, char *dest, int max_len)
{
char temp_str[85];
char acc = 0;
int s0p = sp-1; // to zero base
int k=0;
int cp, cx, c0, cs;
int i = 0;
while(i < len)
{
acc=0;
for(int j=0 ; j<6 ; j++)
{
acc = acc << 1;
cp = (s0p + i) / 6;
cx = bitbytes[cp];
cs = 5 - ((s0p + i) % 6);
c0 = (cx >> (5 - ((s0p + i) % 6))) & 1;
acc |= c0;
i++;
}
temp_str[k] = (char)(acc & 0x3f);
if(acc < 32)
temp_str[k] += 0x40;
k++;
}
temp_str[k] = 0;
int copy_len = wxMin((int)strlen(temp_str), max_len);
strncpy(dest, temp_str, copy_len);
return true;
}
//---------------------------------------------------------------------------------
//
// AIS_Decoder Implementation
//
//---------------------------------------------------------------------------------
BEGIN_EVENT_TABLE(AIS_Decoder, wxWindow)
EVT_CLOSE(AIS_Decoder::OnCloseWindow)
EVT_SOCKET(AIS_SOCKET_ID, AIS_Decoder::OnSocketEvent)
EVT_TIMER(TIMER_AIS1, AIS_Decoder::OnTimerAIS)
EVT_COMMAND(ID_AIS_WINDOW, EVT_AIS, AIS_Decoder::OnEvtAIS)
END_EVENT_TABLE()
AIS_Decoder::AIS_Decoder(int window_id, wxFrame *pParent, const wxString&
AISDataSource):
wxWindow(pParent, window_id, wxPoint(20,30), wxSize(5,5),
wxSIMPLE_BORDER)
{
AISTargetList = new AIS_Target_Hash;
OpenDataSource(pParent, AISDataSource);
Hide();
}
AIS_Decoder::~AIS_Decoder(void)
{
AIS_Target_Hash::iterator it;
AIS_Target_Hash *current_targets = GetTargetList();
for( it = (*current_targets).begin(); it != (*current_targets).end();
++it )
{
AIS_Target_Data *td = it->second;
delete td;
}
delete current_targets;
delete m_pdata_source_string;
}
//----------------------------------------------------------------------------------
// Handle events from AIS Serial Port RX thread
//----------------------------------------------------------------------------------
void AIS_Decoder::OnEvtAIS(wxCommandEvent& event)
{
#define LOCAL_BUFFER_LENGTH 4096
char buf[LOCAL_BUFFER_LENGTH];
switch(event.GetExtraLong())
{
case EVT_AIS_PARSE_RX:
{
if(ais_ps_mutexProtectingTheRXBuffer->Lock() == wxMUTEX_NO_ERROR
)
{
if(RX_BUFFER_FULL == ais_rx_share_buffer_state)
{
int nchar = strlen(ais_rx_share_buffer);
strncpy (buf, ais_rx_share_buffer, wxMin(nchar + 1,
LOCAL_BUFFER_LENGTH - 1));
ais_rx_share_buffer_state = RX_BUFFER_EMPTY;
if(ais_rx_share_buffer_length !=
strlen(ais_rx_share_buffer))
wxLogMessage("Got AIS Event with inconsistent
ais_rx_share_buffer");
}
else
wxLogMessage("Got AIS Event with RX_BUFFER_EMPTY");
ais_ps_mutexProtectingTheRXBuffer->Unlock();
}
/*
if(pStatusBar)
{
wxString buf_nolf(buf);
buf_nolf.RemoveLast();
SetStatusText(buf_nolf.c_str(), 4);
}
*/
Decode(buf);
break;
} //case
} // switch
/* if(bshow_tick)
{
// Show a little heartbeat tick in StatusWindow0 on AIS events
if(tick_idx++ > 6)
tick_idx = 0;
char tick_buf[2];
tick_buf[0] = nmea_tick_chars[tick_idx];
tick_buf[1] = 0;
if(NULL != GetStatusBar())
SetStatusText(tick_buf, 0);
}
*/
}
//----------------------------------------------------------------------------------
// Decode NMEA VDM sentence to AIS Target(s)
//----------------------------------------------------------------------------------
AIS_Error AIS_Decoder::Decode(char *str)
{
AIS_Error ret;
wxString string_to_parse;
// Make some simple tests for validity
if(strlen(str) > 82)
return AIS_NMEAVDM_TOO_LONG;
if(!NMEACheckSumOK(str))
return AIS_NMEAVDM_CHECKSUM_BAD;
if(strncmp(&str[3], "VDM", 3))
return AIS_NMEAVDM_BAD;
// OK, looks like the sentence is OK
// Use a tokenizer to pull out the first 4 fields
wxString string(str);
wxStringTokenizer tkz(string, ",");
wxString token;
token = tkz.GetNextToken(); // !xxVDM
token = tkz.GetNextToken();
nsentences = atoi(token.c_str());
token = tkz.GetNextToken();
isentence = atoi(token.c_str());
token = tkz.GetNextToken();
int sequence_id;
if(token.IsNumber())
sequence_id = atoi(token.c_str());
else
sequence_id = 0;
token = tkz.GetNextToken();
int channel;
if(token.IsNumber())
channel = atoi(token.c_str());
else
channel = 0;
// Now, some decisions
string_to_parse.Clear();
// Simple case first
// First and only part of a one-part sentence
if((1 == nsentences) && (1 == isentence))
{
string_to_parse = tkz.GetNextToken(); // the encapsulated
data
}
else if(nsentences > 1)
{
if(1 == isentence)
{
sentence_accumulator = tkz.GetNextToken(); // the
encapsulated data
}
else
{
sentence_accumulator += tkz.GetNextToken();
}
if(isentence == nsentences)
{
string_to_parse = sentence_accumulator;
}
}
if(!string_to_parse.IsEmpty())
{
// Create the bit accessible string
AIS_Bitstring strbit(string_to_parse.c_str());
// And create a provisional target
AIS_Target_Data *td = Parse_VDMBitstring(&strbit);
AIS_Target_Data *temp = NULL;
AIS_Target_Data *tm = NULL;
if(td)
{
// Search the current AISTargetList for an MMSI match
AIS_Target_Hash::iterator it = AISTargetList->find( td->MMSI );
if(it == AISTargetList->end()) // not found
{
(*AISTargetList)[td->MMSI] = td; // so insert
this entry
tm = td;
// printf("add MMSI %d\n", td->MMSI);
}
else
{
temp = (*AISTargetList)[td->MMSI]; // find current
entry
tm = Merge(temp, td); // merge in new
data
// printf("update MMSI %d\n", td->MMSI);
(*AISTargetList)[td->MMSI] = tm; // replace the
current entry
delete temp; // and kill the
old one
delete td;
// Debug
wxDateTime now = wxDateTime::Now();
now.MakeGMT();
int target_age = now.GetTicks() - tm->ReportTicks;
if(target_age > 500)
printf("AIS:Found very high target age\n");
}
// Update the AIS Target Selectable list
pSelectAIS->DeleteSelectablePoint((void *)temp,
SELTYPE_AISTARGET);
pSelectAIS->AddSelectablePoint(tm->Lat, tm->Lon, (void *)tm,
SELTYPE_AISTARGET);
///debug
SelectItem *pFindSel;
printf("On AIS Target List Update, Inspecting Selectable
List....\n");
wxSelectableItemListNode *node =
pSelectAIS->GetSelectList()->GetFirst();
while(node)
{
pFindSel = node->GetData();
if(pFindSel->m_seltype == SELTYPE_AISTARGET)
{
AIS_Target_Data *tp = (AIS_Target_Data
*)pFindSel->m_pData1;
printf(" MMSI: %d\n", tp->MMSI);
}
node = node->GetNext();
}
/// end debug
ret = AIS_NoError;
}
}
else
ret = AIS_Partial;
return ret;
}
AIS_Target_Data *AIS_Decoder::Merge(AIS_Target_Data *tlast, AIS_Target_Data
*tthis)
{
AIS_Target_Data *result = new AIS_Target_Data;
// Name update
if(tthis->MID == 5)
{
*result = *tlast;
strncpy(&result->ShipName[0], &tthis->ShipName[0], 21);
}
// Position update
else if((tthis->MID == 1) || (tthis->MID == 2) || (tthis->MID == 3))
{
*result = *tthis;
strncpy(&result->ShipName[0], &tlast->ShipName[0], 21);
}
else
*result = *tlast; // default
return result;
}
//----------------------------------------------------------------------------
// Parse a NMEA VDM Bitstring
// Returns a pointer to a new AIS_Target_Data object,
// which is to be owned BY THE CALLER
//----------------------------------------------------------------------------
AIS_Target_Data *AIS_Decoder::Parse_VDMBitstring(AIS_Bitstring *bstr)
{
AIS_Target_Data *res_ptr = NULL;
AIS_Target_Data atd; // temporary
bool parse_result = false;
wxDateTime now = wxDateTime::Now();
now.MakeGMT();
int utc_hour = now.GetHour();
int utc_min = now.GetMinute();
int utc_sec = now.GetSecond();
int utc_day = now.GetDay();
wxDateTime::Month utc_month = now.GetMonth();
int utc_year = now.GetYear();
atd.ReportTicks = now.GetTicks(); // Default is my idea of NOW
// which amy disagee with
target...
int message_ID = bstr->GetInt(1, 6); // Parse on message ID
switch (message_ID)
{
case 1: // Position Report
case 2:
case 3:
{
atd.MID = message_ID;
atd.MMSI = bstr->GetInt(9, 30);
// Debug
// if(atd.MMSI && (atd.MMSI < 100000000))
// atd.MMSI = bstr->GetInt(9, 30);
atd.NavStatus = bstr->GetInt(39, 4);
atd.SOG = 0.1 * (bstr->GetInt(51, 10));
int lon = bstr->GetInt(62, 28);
if(lon & 0x08000000) // negative?
lon |= 0xf0000000;
atd.Lon = lon / 600000.;
int lat = bstr->GetInt(90, 27);
if(lat & 0x04000000) // negative?
lat |= 0xf8000000;
atd.Lat = lat / 600000.;
atd.COG = 0.1 * (bstr->GetInt(117, 12));
atd.HDG = 1.0 * (bstr->GetInt(129, 9));
utc_sec = bstr->GetInt(138, 6);
if((1 == message_ID) || (2 == message_ID)) // decode SOTDMA
per 7.6.7.2.2
{
atd.SyncState = bstr->GetInt(151,2);
atd.SlotTO = bstr->GetInt(153,2);
if((atd.SlotTO == 1) && (atd.SyncState == 0)) // UTCDirect
follows
{
utc_hour = bstr->GetInt(155, 5);
utc_min = bstr->GetInt(160,7);
}
}
if((111111111 == atd.MMSI) || (181.0 == atd.Lon) || (91.0 ==
atd.Lat)) // bogus data
parse_result = false;
if(0 == atd.MMSI)
parse_result = 0;
else
{
// Todo there may be a bug here. Sometimes get invalid
utc_hour, utc_min
if((utc_hour < 24) && (utc_min < 60) && (utc_sec < 60))
{
wxDateTime report_time(utc_day, utc_month, utc_year,
utc_hour, utc_min, utc_sec, 0);
atd.ReportTicks = report_time.GetTicks();
parse_result = true;
}
else
parse_result = false;
}
break;
}
case 5:
{
atd.MID = message_ID;
atd.MMSI = bstr->GetInt(9, 30);
// Debug
// if(atd.MMSI < 100000000)
// atd.MMSI = bstr->GetInt(9, 30);
int DSI = bstr->GetInt(39, 2);
if(0 == DSI)
{
bstr->GetStr(71,42, &atd.CallSign[0], 7);
bstr->GetStr(113,120, &atd.ShipName[0], 20);
atd.ShipType = (unsigned char)bstr->GetInt(233,8);
parse_result = true;
}
break;
}
}
if(true == parse_result)
{
res_ptr = new AIS_Target_Data;
*res_ptr = atd; // shallow copy
is OK
}
return res_ptr;
}
bool AIS_Decoder::NMEACheckSumOK(char *str)
{
unsigned char checksum_value = 0;
int sentence_hex_sum;
int string_length = strlen(str);;
int index = 1; // Skip over the $ at the begining of the sentence
while( index < string_length &&
str[ index ] != '*' &&
str[ index ] != 0x0d &&
str[ index ] != 0x0a )
{
checksum_value ^= str[ index ];
index++;
}
if(string_length > 4)
{
char scanstr[3];
scanstr[0] = str[string_length-4];
scanstr[1] = str[string_length - 3];
scanstr[2] = 0;
sscanf(scanstr, "%2x", &sentence_hex_sum);
if(sentence_hex_sum == checksum_value)
return true;
}
return false;
}
//------------------------------------------------------------------------------------
//
// AIS Target Query Support
//
//------------------------------------------------------------------------------------
// Build a query response
// Resulting string to OWNED BY CALLER
wxString *AIS_Decoder::BuildQueryResult(AIS_Target_Data *td)
{
wxString *res = new wxString;
wxString line;
line.Printf("MMSI: %d\n", td->MMSI);
res->Append(line);
// Clip any unused characters (@) from the name
wxString ts;
char *tp = &td->ShipName[0];
while((*tp) && (*tp != '@'))
ts.Append(*tp++);
ts.Append((char)0);
line.Printf("ShipName: %s\n\n", ts.c_str());
res->Append(line);
line.Printf("Course: %6.0f Deg.\n", td->COG);
res->Append(line);
line.Printf("Speed: %5.2f Kts.\n", td->SOG);
res->Append(line);
wxDateTime now = wxDateTime::Now();
now.MakeGMT();
int target_age = now.GetTicks() - td->ReportTicks;
/// Debug
if((target_age > 500) || (target_age < -500))
{
printf("\nAIS:Found absurd target age\n");
printf("Here is the hash map\n");
AIS_Target_Hash::iterator it;
AIS_Target_Hash *current_targets = GetTargetList();
for( it = (*current_targets).begin(); it !=
(*current_targets).end(); ++it )
{
AIS_Target_Data *tdd = it->second;
int target_aged = now.GetTicks() - tdd->ReportTicks;
printf("Current Target: MMSI: %d target_age:%d\n", tdd->MMSI,
target_aged);
}
}
///
line.Printf("Report Age: %d Sec.\n", target_age);
res->Append(line);
return res;
}
//------------------------------------------------------------------------------------
//
// AIS Data Source Support
//
//------------------------------------------------------------------------------------
AIS_Error AIS_Decoder::OpenDataSource(wxFrame *pParent, const wxString&
AISDataSource)
{
m_pParentEventHandler = pParent->GetEventHandler();
pAIS_Thread = NULL;
m_sock = NULL;
TimerAIS.SetOwner(this, TIMER_AIS1);
TimerAIS.Stop();
m_pdata_source_string = new wxString(AISDataSource);
// Create and manage AIS data stream source
// Decide upon source
wxLogMessage("AIS Data Source
is....%s",m_pdata_source_string->c_str());
// Data Source is private TCP/IP Server
if(m_pdata_source_string->Contains("TCP/IP"))
{
wxString AIS_data_ip;
AIS_data_ip = m_pdata_source_string->Mid(7); // extract
the IP
// Create the socket
m_sock = new wxSocketClient();
// Setup the event handler and subscribe to most events
m_sock->SetEventHandler(*this, AIS_SOCKET_ID);
m_sock->SetNotify(wxSOCKET_CONNECTION_FLAG |
wxSOCKET_INPUT_FLAG |
wxSOCKET_LOST_FLAG);
m_sock->Notify(TRUE);
m_busy = FALSE;
// Build the target address
// n.b. Win98
// wxIPV4address::Hostname() uses sockets function gethostbyname() for
address resolution
// Implications...Either name target must exist in c:\windows\hosts, or
// a DNS server must be active on the network.
// If neither true, then wxIPV4address::Hostname() will block
(forever?)....
//
// Workaround....
// Use a thread to try the name lookup, in case it hangs
DNSTestThread *ptest_thread = NULL;
ptest_thread = new DNSTestThread(AIS_data_ip);
ptest_thread->Run(); // Run the thread from
::Entry()
// Sleep and loop for N seconds
#define SLEEP_TEST_SEC 2
for(int is=0 ; is<SLEEP_TEST_SEC * 10 ; is++)
{
wxMilliSleep(100);
if(s_dns_test_flag)
break;
}
if(!s_dns_test_flag)
{
wxString msg(AIS_data_ip);
msg.Prepend("Could not resolve TCP/IP host '");
msg.Append("'\n Suggestion: Try 'xxx.xxx.xxx.xxx'
notation");
wxMessageDialog md(this, msg, "OpenCPN Message",
wxICON_ERROR );
md.ShowModal();
m_sock->Notify(FALSE);
m_sock->Destroy();
m_sock = NULL;
return AIS_NO_TCP;
}
// Resolved the name, somehow, so Connect() the socket
addr.Hostname(AIS_data_ip);
addr.Service(3047/*GPSD_PORT_NUMBER*/);
m_sock->Connect(addr, FALSE); // Non-blocking connect
}
// AIS Data Source is specified serial port
else if(m_pdata_source_string->Contains("Serial"))
{
wxString comx;
comx = m_pdata_source_string->Mid(7); // either "COM1"
style or "/dev/ttyS0" style
#ifdef __WXMSW__
// As a quick check, verify that the specified port is available
HANDLE m_hSerialComm = CreateFile(comx.c_str(), // Port
Name
GENERIC_READ,
0,
NULL,
OPEN_EXISTING,
0,
NULL);
if(m_hSerialComm == INVALID_HANDLE_VALUE)
{
wxString msg(comx);
msg.Prepend(" Could not open AIS serial port '");
msg.Append("'\nSuggestion: Try closing other
applications.");
wxMessageDialog md(this, msg, "OpenCPN Message",
wxICON_ERROR );
md.ShowModal();
return AIS_NO_SERIAL;
}
else
CloseHandle(m_hSerialComm);
// Kick off the AIS RX thread
pAIS_Thread = new OCP_AIS_Thread(this, comx);
pAIS_Thread->Run();
#endif
#ifdef __LINUX__
// Kick off the NMEA RX thread
pAIS_Thread = new OCP_AIS_Thread(this, comx);
pAIS_Thread->Run();
#endif
}
TimerAIS.Start(TIMER_AIS_MSEC,wxTIMER_CONTINUOUS);
return AIS_NoError;
}
void AIS_Decoder::OnCloseWindow(wxCloseEvent& event)
{
// Kill off the TCP/IP Socket if alive
if(m_sock)
{
m_sock->Notify(FALSE);
m_sock->Destroy();
TimerAIS.Stop();
}
// Kill off the RX Thread if alive
// Todo Need to fix this by re-building the comm thread to not use
blocking event structure
// Also affects the NMEA thread in nmea.cpp
if(pAIS_Thread)
{
pAIS_Thread->Delete(); //was kill();
#ifdef __WXMSW__
// wxSleep(1);
#else
sleep(2);
#endif
}
}
void AIS_Decoder::GetSource(wxString& source)
{
source = *m_pdata_source_string;
}
void AIS_Decoder::Pause(void)
{
TimerAIS.Stop();
if(m_sock)
m_sock->Notify(FALSE);
}
void AIS_Decoder::UnPause(void)
{
TimerAIS.Start(TIMER_AIS_MSEC,wxTIMER_CONTINUOUS);
if(m_sock)
m_sock->Notify(TRUE);
}
void AIS_Decoder::OnSocketEvent(wxSocketEvent& event)
{
#define RD_BUF_SIZE 200
int nBytes;
char *bp;
char buf[RD_BUF_SIZE + 1];
int char_count;
switch(event.GetSocketEvent())
{
case wxSOCKET_INPUT : // from Daemon
m_sock->SetFlags(wxSOCKET_NOWAIT);
// Read the reply, one character at a time, looking for 0x0a (lf)
bp = buf;
char_count = 0;
while (char_count < RD_BUF_SIZE)
{
m_sock->Read(bp, 1);
nBytes = m_sock->LastCount();
if(*bp == 0x0a)
{
bp++;
break;
}
bp++;
char_count++;
}
*bp = 0; // end string
// Validate the string
if(!strncmp((const char *)buf, "!AIVDM", 6))
{
Decode(buf);
// Signal the main program thread
// wxCommandEvent event( EVT_AIS, ID_AIS_WINDOW );
// event.SetEventObject( (wxObject *)this );
// event.SetExtraLong(EVT_AIS_DIRECT);
// m_pParentEventHandler->AddPendingEvent(event);
}
case wxSOCKET_LOST :
case wxSOCKET_CONNECTION :
default :
break;
}
}
//static int itime;
//static int istr;
void AIS_Decoder::OnTimerAIS(wxTimerEvent& event)
{
TimerAIS.Stop();
// Scrub the target hash list
// removing any targets older than stipulated age
// Todo Add a method to set this parameter
int death_age_seconds = 300;
wxDateTime now = wxDateTime::Now();
now.MakeGMT();
AIS_Target_Hash::iterator it;
AIS_Target_Hash *current_targets = GetTargetList();
// printf("\n");
for( it = (*current_targets).begin(); it != (*current_targets).end();
++it )
{
AIS_Target_Data *td = it->second;
int target_age = now.GetTicks() - td->ReportTicks;
// printf("Current Target: MMSI: %d target_age:%d\n", td->MMSI,
target_age);
if(target_age > death_age_seconds)
{
// printf(" erase MMSI %d\n", td->MMSI);
current_targets->erase(it);
pSelectAIS->DeleteSelectablePoint((void *)td,
SELTYPE_AISTARGET);
delete td;
break; // kill only one per tick, since iterator
becomes invalid...
}
}
//--------------TEST DATA strings
#if(0)
char str[82];
if(1)
{
if(itime++ > 2)
{
itime = 0;
strcpy(str, test_str[istr]);
istr++;
if(istr > 23)
istr = 0;
Decode(str);
}
}
#endif
TimerAIS.Start(TIMER_AIS_MSEC,wxTIMER_CONTINUOUS);
}
//-------------------------------------------------------------------------------------------------------------
//
// AIS Serial Input Thread
//
// This thread manages reading the AIS data stream from the declared
serial port
//
//-------------------------------------------------------------------------------------------------------------
// Inter-thread communication event implementation
DEFINE_EVENT_TYPE(EVT_AIS)
//-------------------------------------------------------------------------------------------------------------
// OCP_AIS_Thread Implementation
//-------------------------------------------------------------------------------------------------------------
// ctor
OCP_AIS_Thread::OCP_AIS_Thread(wxWindow *MainWindow, const char
*pszPortName)
{
m_pMainEventHandler = MainWindow->GetEventHandler();
ais_rx_share_buffer_state = RX_BUFFER_EMPTY;
m_pPortName = new wxString(pszPortName);
rx_buffer = new char[RX_BUFFER_SIZE + 1];
put_ptr = rx_buffer;
tak_ptr = rx_buffer;
ais_ps_mutexProtectingTheRXBuffer = new wxMutex;
Create();
}
OCP_AIS_Thread::~OCP_AIS_Thread(void)
{
delete m_pPortName;
delete rx_buffer;
delete ais_ps_mutexProtectingTheRXBuffer;
}
void OCP_AIS_Thread::OnExit(void)
{
// Mark the global status as dead, gone
pAIS_Thread = NULL;
}
#ifdef __LINUX__
#if defined (HAVE_SYS_TERMIOS_H)
#include <sys/termios.h>
#else
#if defined (HAVE_TERMIOS_H)
#include <termios.h>
#endif
#endif
#include <sys/termios.h>
#endif
// Sadly, the thread itself must implement the underlying OS serial
port
// in a very machine specific way....
#ifdef __LINUX__
// Entry Point
void *OCP_AIS_Thread::Entry()
{
// Allocate the termios data structures
pttyset = (termios *)malloc(sizeof (termios));
pttyset_old = (termios *)malloc(sizeof (termios));
// Open the serial port.
if ((m_ais_fd = open(m_pPortName->c_str(), O_RDWR|O_NONBLOCK|O_NOCTTY))
< 0)
{
wxLogMessage("AIS NMEA input device open failed: %s\n",
m_pPortName->c_str());
return 0;
}
{
(void)cfsetispeed(pttyset, B38400);
(void)cfsetospeed(pttyset, (speed_t)B38400);
(void)tcsetattr(m_ais_fd, TCSANOW, pttyset);
(void)tcflush(m_ais_fd, TCIOFLUSH);
}
if (isatty(m_ais_fd)!=0)
{
/* Save original terminal parameters */
if (tcgetattr(m_ais_fd,pttyset_old) != 0)
{
wxLogMessage("AIS NMEA input device getattr failed: %s\n",
m_pPortName->c_str());
return 0;
}
(void)memcpy(pttyset, pttyset_old, sizeof(termios));
// Build the new parms off the old
// Set blocking/timeout behaviour
memset(pttyset->c_cc,0,sizeof(pttyset->c_cc));
// pttyset->c_cc[VMIN] = 1;
pttyset->c_cc[VTIME] = 11; // 1.1 sec timeout
/*
* No Flow Control
*/
pttyset->c_cflag &= ~(PARENB | PARODD | CRTSCTS);
pttyset->c_cflag |= CREAD | CLOCAL;
pttyset->c_iflag = pttyset->c_oflag = pttyset->c_lflag = (tcflag_t) 0;
int stopbits = 1;
char parity = 'N';
pttyset->c_iflag &=~ (PARMRK | INPCK);
pttyset->c_cflag &=~ (CSIZE | CSTOPB | PARENB | PARODD);
pttyset->c_cflag |= (stopbits==2 ? CS7|CSTOPB : CS8);
switch (parity)
{
case 'E':
pttyset->c_iflag |= INPCK;
pttyset->c_cflag |= PARENB;
break;
case 'O':
pttyset->c_iflag |= INPCK;
pttyset->c_cflag |= PARENB | PARODD;
break;
}
pttyset->c_cflag &=~ CSIZE;
pttyset->c_cflag |= (CSIZE & (stopbits==2 ? CS7 : CS8));
if (tcsetattr(m_ais_fd, TCSANOW, pttyset) != 0)
{
wxLogMessage("NMEA input device setattr failed: %s\n",
m_pPortName->c_str());
return 0;
}
(void)tcflush(m_ais_fd, TCIOFLUSH);
}
bool not_done = true;
bool nl_found;
char next_byte = 0;
ssize_t newdata = 0;
// The main loop
// printf("starting\n");
while(not_done)
{
if(TestDestroy())
{
not_done = false; // smooth exit
// printf("smooth exit\n");
}
//#define oldway 1
#ifdef oldway
// Blocking, timeout protected read of one character at a time
// Timeout value is set by c_cc[VTIME]
// Storing incoming characters in circular buffer
// And watching for new line character
// On new line character, send notification to parent
newdata = read(m_ais_fd, &next_byte, 1); // blocking read
of one char
// return (-1)
if no data available, timeout
#else
// Kernel I/O multiplexing provides a cheap way to wait for chars
fd_set rfds;
struct timeval tv;
int retval;
// m_ais_fd to see when it has input.
FD_ZERO(&rfds);
FD_SET(m_ais_fd, &rfds);
// Wait up to 1 second
tv.tv_sec = 1;
tv.tv_usec = 0;
newdata = 0;
// wait for a read available on m_ais_fd, we don't care about write or
exceptions
retval = select(FD_SETSIZE, &rfds, NULL, NULL, &tv);
// if (retval == -1)
// perror("select()");
// else if (retval)
// printf("Data is available now.\n");
// FD_ISSET(0, &rfds) will be true.
// else
// printf("No data within one seconds.\n");
if(FD_ISSET(m_ais_fd, &rfds) && (retval != -1) && retval)
newdata = read(m_ais_fd, &next_byte, 1); // blocking
read of one char
// bound to
succeed
#endif
if(newdata > 0)
{
nl_found = false;
*put_ptr++ = next_byte;
if((put_ptr - rx_buffer) > RX_BUFFER_SIZE)
put_ptr = rx_buffer;
if(0x0a == next_byte)
nl_found = true;
// Found a NL char, thus end of message?
if(nl_found)
{
char *tptr;
char *ptmpbuf;
char temp_buf[RX_BUFFER_SIZE];
// If the shared buffer is available....
if(ais_ps_mutexProtectingTheRXBuffer->Lock() ==
wxMUTEX_NO_ERROR )
{
if(RX_BUFFER_EMPTY == ais_rx_share_buffer_state)
{
// Copy the message into the rx_shared_buffer
tptr = tak_ptr;
ptmpbuf = temp_buf;
while((*tptr != 0x0a) && (tptr != put_ptr))
{
*ptmpbuf++ = *tptr++;
if((tptr - rx_buffer) > RX_BUFFER_SIZE)
tptr = rx_buffer;
}
if(*tptr ==
0x0a) // well formed sentence
{
*ptmpbuf++ = *tptr++;
if((tptr - rx_buffer) > RX_BUFFER_SIZE)
tptr = rx_buffer;
*ptmpbuf = 0;
tak_ptr = tptr;
strcpy(ais_rx_share_buffer, temp_buf);
ais_rx_share_buffer_state = RX_BUFFER_FULL;
ais_rx_share_buffer_length =
strlen(ais_rx_share_buffer);
// Signal the main program thread
wxCommandEvent event( EVT_AIS, ID_AIS_WINDOW );
event.SetEventObject( (wxObject *)this );
event.SetExtraLong(EVT_AIS_PARSE_RX);
m_pMainEventHandler->AddPendingEvent(event);
}
}
}
// Release the MUTEX
ais_ps_mutexProtectingTheRXBuffer->Unlock();
} //if nl
} // if newdata > 0
} // the big while...
// Close the port cleanly
/* this is the clean way to do it */
// pttyset_old->c_cflag |= HUPCL;
// (void)tcsetattr(m_ais_fd,TCSANOW,pttyset_old);
(void)close(m_ais_fd);
free (pttyset);
free (pttyset_old);
return 0;
}
#endif //__LINUX__
#ifdef __WXMSW__
// Entry Point
void *OCP_AIS_Thread::Entry()
{
bool not_done;
DWORD dwRead;
BOOL fWaitingOnRead = FALSE;
OVERLAPPED osReader = {0};
#define READ_BUF_SIZE 100
char buf[READ_BUF_SIZE];
#define READ_TIMEOUT 500 // milliseconds
DWORD dwRes;
// Set up the serial port
m_hSerialComm = CreateFile(m_pPortName->c_str(), // Port Name
GENERIC_READ, //
Desired Access
0, //
Shared Mode
NULL, //
Security
OPEN_EXISTING, //
Creation Disposition
FILE_FLAG_OVERLAPPED,
NULL); //
Overlapped
if(m_hSerialComm == INVALID_HANDLE_VALUE)
{
error = ::GetLastError();
goto fail_point;
}
if(!SetupComm(m_hSerialComm, 1024, 1024))
goto fail_point;
DCB dcbConfig;
if(GetCommState(m_hSerialComm, &dcbConfig)) // Configuring
Serial Port Settings
{
dcbConfig.BaudRate = 38400;
dcbConfig.ByteSize = 8;
dcbConfig.Parity = NOPARITY;
dcbConfig.StopBits = ONESTOPBIT;
dcbConfig.fBinary = TRUE;
dcbConfig.fParity = TRUE;
}
else
goto fail_point;
if(!SetCommState(m_hSerialComm, &dcbConfig))
goto fail_point;
COMMTIMEOUTS commTimeout;
TimeOutInSec = 2;
if(GetCommTimeouts(m_hSerialComm, &commTimeout)) // Configuring Read &
Write Time Outs
{
commTimeout.ReadIntervalTimeout = 1000*TimeOutInSec;
commTimeout.ReadTotalTimeoutConstant = 1000*TimeOutInSec;
commTimeout.ReadTotalTimeoutMultiplier = 0;
commTimeout.WriteTotalTimeoutConstant = 1000*TimeOutInSec;
commTimeout.WriteTotalTimeoutMultiplier = 0;
}
else
goto fail_point;
if(!SetCommTimeouts(m_hSerialComm, &commTimeout))
goto fail_point;
// Set up event specification
if(!SetCommMask(m_hSerialComm, EV_RXCHAR)) // Setting Event Type
goto fail_point;
// Create the overlapped event. Must be closed before exiting
// to avoid a handle leak.
osReader.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
not_done = true;
// The main loop
while(not_done)
{
if(TestDestroy())
not_done = false; // smooth
exit
if (osReader.hEvent == NULL)
// Error creating overlapped event; abort.
goto fail_point;
if (!fWaitingOnRead)
{
// Issue read operation.
if (!ReadFile(m_hSerialComm, buf, 10, &dwRead, &osReader))
{
if (GetLastError() != ERROR_IO_PENDING) // read not
delayed?
// Error in communications; report it.
goto fail_point;
else
fWaitingOnRead = TRUE;
}
else
{
// read completed immediately
HandleASuccessfulRead(buf, dwRead);
}
}
if (fWaitingOnRead)
{
dwRes = WaitForSingleObject(osReader.hEvent, READ_TIMEOUT);
switch(dwRes)
{
// Read completed.
case WAIT_OBJECT_0:
if (!GetOverlappedResult(m_hSerialComm, &osReader,
&dwRead, FALSE))
// Error in communications; report it.
goto fail_point;
else
// Read completed successfully.
HandleASuccessfulRead(buf, dwRead);
// Reset flag so that another opertion can be issued.
fWaitingOnRead = FALSE;
break;
case WAIT_TIMEOUT:
// Operation isn't complete yet. fWaitingOnRead flag isn't
// changed since I'll loop back around, and I don't want
// to issue another read until the first one finishes.
//
// This is a good time to do some background work.
break;
default:
// Error in the WaitForSingleObject; abort.
// This indicates a problem with the OVERLAPPED structure's
// event handle.
break;
} // switch
} // if
} // big while
fail_point:
return 0;
}
bool OCP_AIS_Thread::HandleASuccessfulRead(char *buf, int
dwIncommingReadSize)
{
bool nl_found;
char *bp = buf;
if(dwIncommingReadSize > 0)
{
int nchar = dwIncommingReadSize;
while(nchar)
{
char c = *bp;
*put_ptr++ = *bp++;
if((put_ptr - rx_buffer) > RX_BUFFER_SIZE)
put_ptr = rx_buffer;
if(0x0a == c)
nl_found = true;
nchar--;
}
}
else
{
error = ::GetLastError();
return false;
}
// Found a NL char, thus end of message?
if(nl_found)
{
char *tptr;
char *ptmpbuf;
char temp_buf[RX_BUFFER_SIZE];
bool partial = false;
while (!partial)
{
// If the shared buffer is available....
if(ais_ps_mutexProtectingTheRXBuffer->Lock() ==
wxMUTEX_NO_ERROR )
{
if(RX_BUFFER_EMPTY == ais_rx_share_buffer_state)
{
// Copy the message into the rx_shared_buffer
tptr = tak_ptr;
ptmpbuf = temp_buf;
while((*tptr != 0x0a) && (tptr != put_ptr))
{
*ptmpbuf++ = *tptr++;
if((tptr - rx_buffer) > RX_BUFFER_SIZE)
tptr = rx_buffer;
}
if((*tptr == 0x0a) && (tptr !=
put_ptr)) // well formed sentence
{
*ptmpbuf++ = *tptr++;
if((tptr - rx_buffer) > RX_BUFFER_SIZE)
tptr = rx_buffer;
*ptmpbuf = 0;
tak_ptr = tptr;
strcpy(ais_rx_share_buffer, temp_buf);
ais_rx_share_buffer_state =
RX_BUFFER_FULL;
ais_rx_share_buffer_length =
strlen(ais_rx_share_buffer);
// Signal the main program thread
wxCommandEvent event( EVT_AIS,
ID_AIS_WINDOW );
event.SetEventObject( (wxObject *)this );
event.SetExtraLong(EVT_AIS_PARSE_RX);
m_pMainEventHandler->AddPendingEvent(event);
}
else
{
partial = true;
//
wxLogMessage("partial");
}
}
}
// Release the MUTEX
ais_ps_mutexProtectingTheRXBuffer->Unlock();
} // while
} // if nl
return true;
}
#endif
