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