#include "NMEA0183DataToN2K.h" #include "NMEA0183Messages.h" #include "N2kMessages.h" #include "ConverterList.h" #include #include #include #include "NMEA0183AIStoNMEA2000.h" #include "GwXDRMappings.h" #include "GwNmea0183Msg.h" static const double mToFathoms=0.546806649; static const double mToFeet=3.2808398950131; NMEA0183DataToN2K::NMEA0183DataToN2K(GwLog *logger, GwBoatData *boatData,N2kSender callback) { this->sender = callback; this->logger = logger; this->boatData=boatData; LOG_DEBUG(GwLog::LOG,"NMEA0183DataToN2K created %p",this); } class NMEA0183DataToN2KFunctions : public NMEA0183DataToN2K { private: MyAisDecoder *aisDecoder=NULL; ConverterList converters; std::map lastSends; GwXDRMappings *xdrMappings; class WaypointNumber{ public: unsigned long id; unsigned long lastUsed; WaypointNumber(){} WaypointNumber(unsigned long id,unsigned long ts){ this->id=id; this->lastUsed=ts; } }; const size_t MAXWAYPOINTS=100; std::map waypointMap; uint8_t waypointId=1; uint8_t getWaypointId(const char *name){ String wpName(name); auto it=waypointMap.find(wpName); if (it != waypointMap.end()){ it->second.lastUsed=millis(); return it->second.id; } unsigned long now=millis(); auto oldestIt=waypointMap.begin(); uint8_t newNumber=0; if (waypointMap.size() > MAXWAYPOINTS){ LOG_DEBUG(GwLog::DEBUG+1,"removing oldest from waypoint map"); for (it=waypointMap.begin();it!=waypointMap.end();it++){ if (oldestIt->second.lastUsed > it->second.lastUsed){ oldestIt=it; } } newNumber=oldestIt->second.id; waypointMap.erase(oldestIt); } else{ waypointId++; newNumber=waypointId; } WaypointNumber newWp(newNumber,now); waypointMap[wpName]=newWp; return newWp.id; } bool send(tN2kMsg &msg,String key,unsigned long minDiff,int sourceId){ unsigned long now=millis(); unsigned long pgn=msg.PGN; if (key == "") key=String(msg.PGN); auto it=lastSends.find(key); if (it == lastSends.end()){ lastSends[key]=now; sender(msg,sourceId); return true; } if ((it->second + minDiff) <= now){ lastSends[key]=now; sender(msg,sourceId); return true; } LOG_DEBUG(GwLog::DEBUG+1,"skipped n2k message %d",msg.PGN); return false; } bool send(tN2kMsg &msg, int sourceId,String key=""){ return send(msg,key,config.min2KInterval,sourceId); } bool updateDouble(GwBoatItem *target,double v, int sourceId){ if (v != NMEA0183DoubleNA){ return target->update(v,sourceId); } return false; } bool updateUint32(GwBoatItem *target,uint32_t v, int sourceId){ if (v != NMEA0183UInt32NA){ return target->update(v,sourceId); } return false; } uint32_t getUint32(GwBoatItem *src){ return src->getDataWithDefault(N2kUInt32NA); } #define UD(item) updateDouble(boatData->item, item, msg.sourceId) #define UI(item) updateUint32(boatData->item, item, msg.sourceId) tN2kXTEMode xteMode(const char modeChar){ switch(modeChar){ case 'D': return N2kxtem_Differential; case 'E': return N2kxtem_Estimated; case 'M': return N2kxtem_Manual; case 'S': return N2kxtem_Simulator; default: break; } return N2kxtem_Autonomous; } class XdrMappingAndValue{ public: GwXDRFoundMapping mapping; double value; XdrMappingAndValue(GwXDRFoundMapping &mapping,double value){ this->mapping=mapping; this->value=value; } int field(){return mapping.definition->field;} int selector(){return mapping.definition->selector;} }; typedef std::vector XdrMappingList; /** * find a mapping for a different field from the same * category with similar instanceId and selector */ GwXDRFoundMapping getOtherFieldMapping(GwXDRFoundMapping &found, int field){ if (found.empty) return GwXDRFoundMapping(); return xdrMappings->getMapping(found.definition->category, found.definition->selector, field, found.instanceId); } double getOtherFieldValue(GwXDRFoundMapping &found, int field){ GwXDRFoundMapping other=getOtherFieldMapping(found,field); if (other.empty) return N2kDoubleNA; LOG_DEBUG(GwLog::DEBUG+1,"found other field mapping %s",other.definition->toString().c_str()); return boatData->getDataWithDefault(N2kDoubleNA,&other); } /** * fill all the fields we potentially need for the n2k message * we take the current transducer value from the XdrMappingAndValue and * try to find a mapping with similar category/instance/selector but different * field id for the other fields * if such a mapping exists we try to fetch the entry from boatData * if we at least inserted one valid value into the field list * we return true so that we can send out the message */ bool fillFieldList(XdrMappingAndValue ¤t,double *list,int numFields,int start=0){ bool rt=false; for (int i=start;itoString().c_str(), start,numFields, current.value, rt?"true":"false"); return rt; } String buildN2KKey(const tN2kMsg &msg,GwXDRFoundMapping &found){ String rt(msg.PGN); rt+="."; rt+=String(found.definition->selector); rt+="."; rt+=String(found.instanceId); return rt; } int8_t fromDouble(double v){ if (N2kIsNA(v)) return N2kInt8NA; return v; } void convertXDR(const SNMEA0183Msg &msg){ XdrMappingList foundMappings; for (int offset=0;offset <= (msg.FieldCount()-4);offset+=4){ //parse next transducer String type=msg.Field(offset); if (msg.FieldLen(offset+1) < 1) continue; //empty value double value=atof(msg.Field(offset+1)); String unit=msg.Field(offset+2); String transducerName=msg.Field(offset+3); GwXDRFoundMapping found=xdrMappings->getMapping(transducerName,type,unit); if (found.empty) continue; value=found.valueFromXdr(value); if (!boatData->update(value,msg.sourceId,&found)) continue; LOG_DEBUG(GwLog::DEBUG+1,"found mapped XDR %s:%s, value %f", transducerName.c_str(), found.definition->toString().c_str(), value); foundMappings.push_back(XdrMappingAndValue(found,value)); } static const int maxFields=20; double fields[maxFields]; //currently we will build similar n2k messages if there are //multiple transducers in one XDR //but we finally rely on the send interval filter //to not send them multiple times //it could be optimized by checking if we already created //a message with similar key in this round and skipp the fillFieldList //in this case for (auto it=foundMappings.begin();it != foundMappings.end();it++){ XdrMappingAndValue current=*it; tN2kMsg n2kMsg; switch (current.mapping.definition->category) { case XDRFLUID: if (fillFieldList(current, fields, 2)) { SetN2kPGN127505(n2kMsg, current.mapping.instanceId, (tN2kFluidType)(current.selector()), fields[0], fields[1]); send(n2kMsg,msg.sourceId, buildN2KKey(n2kMsg, current.mapping)); } break; case XDRBAT: if (fillFieldList(current, fields, 3)) { SetN2kPGN127508(n2kMsg, current.mapping.instanceId, fields[0], fields[1], fields[2]); send(n2kMsg,msg.sourceId, buildN2KKey(n2kMsg, current.mapping)); } break; case XDRTEMP: if (fillFieldList(current,fields,2)){ SetN2kPGN130312(n2kMsg,1,current.mapping.instanceId, (tN2kTempSource)(current.selector()), fields[0],fields[1]); send(n2kMsg,msg.sourceId,buildN2KKey(n2kMsg,current.mapping)); } break; case XDRHUMIDITY: if (fillFieldList(current,fields,2)){ SetN2kPGN130313(n2kMsg,1,current.mapping.instanceId, (tN2kHumiditySource)(current.selector()), fields[0], fields[1] ); send(n2kMsg,msg.sourceId,buildN2KKey(n2kMsg,current.mapping)); } break; case XDRPRESSURE: if (fillFieldList(current,fields,1)){ SetN2kPGN130314(n2kMsg,1,current.mapping.instanceId, (tN2kPressureSource)(current.selector()), fields[0]); send(n2kMsg,msg.sourceId,buildN2KKey(n2kMsg,current.mapping)); } break; case XDRENGINE: if (current.field() <= 9) { if (fillFieldList(current, fields, 10)) { SetN2kPGN127489(n2kMsg, current.mapping.instanceId, fields[0], fields[1], fields[2], fields[3], fields[4], fields[5], fields[6], fields[7], fromDouble(fields[8]), fromDouble(fields[9]), tN2kEngineDiscreteStatus1(), tN2kEngineDiscreteStatus2()); send(n2kMsg,msg.sourceId, buildN2KKey(n2kMsg, current.mapping)); } } else{ if (fillFieldList(current, fields, 13,10)){ SetN2kPGN127488(n2kMsg,current.mapping.instanceId, fields[10],fields[11],fromDouble(fields[12])); send(n2kMsg,msg.sourceId, buildN2KKey(n2kMsg, current.mapping)); } } break; case XDRATTITUDE: if (fillFieldList(current,fields,3)){ SetN2kPGN127257(n2kMsg,current.mapping.instanceId,fields[0],fields[1],fields[2]); send(n2kMsg,msg.sourceId,buildN2KKey(n2kMsg,current.mapping)); } default: continue; } } } void convertRMB(const SNMEA0183Msg &msg) { LOG_DEBUG(GwLog::DEBUG + 1, "convert RMB"); tRMB rmb; if (! NMEA0183ParseRMB_nc(msg,rmb)){ LOG_DEBUG(GwLog::DEBUG, "failed to parse RMB %s", msg.line); return; } tN2kMsg n2kMsg; if (boatData->XTE->update(rmb.xte,msg.sourceId)){ tN2kXTEMode mode=N2kxtem_Autonomous; if (msg.FieldCount() > 13){ const char *modeChar=msg.Field(13); mode=xteMode(*modeChar); } SetN2kXTE(n2kMsg,1,mode,false,rmb.xte); send(n2kMsg,msg.sourceId); } uint8_t destinationId=getWaypointId(rmb.destID); uint8_t sourceId=getWaypointId(rmb.originID); if (boatData->DTW->update(rmb.dtw,msg.sourceId) && boatData->BTW->update(rmb.btw,msg.sourceId) && boatData->WPLat->update(rmb.latitude,msg.sourceId) && boatData->WPLon->update(rmb.longitude,msg.sourceId) ){ SetN2kNavigationInfo(n2kMsg,1,rmb.dtw,N2khr_true, false, (rmb.arrivalAlarm == 'A'), N2kdct_GreatCircle, //see e.g. https://manuals.bandg.com/discontinued/NMEA_FFD_User_Manual.pdf pg 21 N2kDoubleNA, N2kUInt16NA, N2kDoubleNA, rmb.btw, sourceId, destinationId, rmb.latitude, rmb.longitude, rmb.vmg ); send(n2kMsg,msg.sourceId); SetN2kPGN129285(n2kMsg,sourceId,1,1,true,true,"default"); AppendN2kPGN129285(n2kMsg,destinationId,rmb.destID,rmb.latitude,rmb.longitude); send(n2kMsg,msg.sourceId); } } void convertRMC(const SNMEA0183Msg &msg) { double GPST=0, LAT=0, LON=0, COG=0, SOG=0, VAR=0; unsigned long GPSD=0; time_t DateTime; char status; if (!NMEA0183ParseRMC_nc(msg, GPST, status, LAT, LON, COG, SOG, GPSD, VAR, &DateTime)) { LOG_DEBUG(GwLog::DEBUG, "failed to parse RMC %s", msg.line); return; } if (status != 'A' && status != 'a'){ LOG_DEBUG(GwLog::DEBUG, "invalid status %c for RMC %s",status, msg.line); return; } lastRmc=millis(); //we received an RMC that is not from us tN2kMsg n2kMsg; if ( UD(GPST) && UI(GPSD) ) { SetN2kSystemTime(n2kMsg, 1, GPSD, GPST); send(n2kMsg,msg.sourceId); } if (UD(LAT) && UD(LON)){ SetN2kLatLonRapid(n2kMsg,LAT,LON); send(n2kMsg,msg.sourceId); } if (UD(COG) && UD(SOG)){ SetN2kCOGSOGRapid(n2kMsg,1,N2khr_true,COG,SOG); send(n2kMsg,msg.sourceId); } if (UD(VAR)){ SetN2kMagneticVariation(n2kMsg,1,N2kmagvar_Calc, getUint32(boatData->GPSD), VAR); send(n2kMsg,msg.sourceId); } } void convertAIVDX(const SNMEA0183Msg &msg){ aisDecoder->sourceId=msg.sourceId; aisDecoder->handleMessage(msg.line); } void convertMWV(const SNMEA0183Msg &msg){ double WindAngle,WindSpeed; tNMEA0183WindReference Reference; if (!NMEA0183ParseMWV_nc(msg, WindAngle, Reference,WindSpeed)) { LOG_DEBUG(GwLog::DEBUG, "failed to parse MWV %s", msg.line); return; } tN2kMsg n2kMsg; bool shouldSend=false; WindAngle=formatDegToRad(WindAngle); GwConverterConfig::WindMapping mapping; switch(Reference){ case NMEA0183Wind_Apparent: shouldSend=updateDouble(boatData->AWA,WindAngle,msg.sourceId) && updateDouble(boatData->AWS,WindSpeed,msg.sourceId); if (WindSpeed != NMEA0183DoubleNA) boatData->MaxAws->updateMax(WindSpeed,msg.sourceId); mapping=config.findWindMapping(GwConverterConfig::WindMapping::AWA_AWS); break; case NMEA0183Wind_True: shouldSend=updateDouble(boatData->TWA,WindAngle,msg.sourceId) && updateDouble(boatData->TWS,WindSpeed,msg.sourceId); if (WindSpeed != NMEA0183DoubleNA) boatData->MaxTws->updateMax(WindSpeed,msg.sourceId); mapping=config.findWindMapping(GwConverterConfig::WindMapping::TWA_TWS); break; default: LOG_DEBUG(GwLog::DEBUG,"unknown wind reference %d in %s",(int)Reference,msg.line); } //TODO: try to compute TWD and get mapping for this one if (shouldSend && mapping.valid){ SetN2kWindSpeed(n2kMsg,1,WindSpeed,WindAngle,mapping.n2kType); send(n2kMsg,msg.sourceId,String(n2kMsg.PGN)+String((int)mapping.n2kType)); } } void convertVWR(const SNMEA0183Msg &msg) { double WindAngle = NMEA0183DoubleNA, WindSpeed = NMEA0183DoubleNA; if (msg.FieldCount() < 8 || msg.FieldLen(0) < 1) { LOG_DEBUG(GwLog::DEBUG, "failed to parse VWR %s", msg.line); return; } WindAngle = atof(msg.Field(0)); char direction = msg.Field(1)[0]; if (direction == 'L' && WindAngle < 180) WindAngle = 360 - WindAngle; WindAngle = formatDegToRad(WindAngle); if (msg.FieldLen(2) > 0 && msg.Field(3)[0] == 'N') { WindSpeed = atof(msg.Field(2)) * knToms; } else if (msg.FieldLen(4) > 0 && msg.Field(5)[0] == 'M') { WindSpeed = atof(msg.Field(4)); } else if (msg.FieldLen(6) > 0 && msg.Field(7)[0] == 'K') { WindSpeed = atof(msg.Field(6)) * 1000.0 / 3600.0; } if (WindSpeed == NMEA0183DoubleNA) { logger->logDebug(GwLog::DEBUG, "no wind speed in VWR %s", msg.line); return; } tN2kMsg n2kMsg; bool shouldSend = false; shouldSend = updateDouble(boatData->AWA, WindAngle, msg.sourceId) && updateDouble(boatData->AWS, WindSpeed, msg.sourceId); if (WindSpeed != NMEA0183DoubleNA) boatData->MaxAws->updateMax(WindSpeed,msg.sourceId); if (shouldSend) { const GwConverterConfig::WindMapping mapping=config.findWindMapping(GwConverterConfig::WindMapping::AWA_AWS); if (mapping.valid){ SetN2kWindSpeed(n2kMsg, 1, WindSpeed, WindAngle, mapping.n2kType); send(n2kMsg,msg.sourceId,String(n2kMsg.PGN)+String((int)mapping.n2kType)); } } } void convertMWD(const SNMEA0183Msg &msg) { double WindDirection = NMEA0183DoubleNA, WindDirectionMagnetic=NMEA0183DoubleNA, WindSpeed = NMEA0183DoubleNA; if (msg.FieldCount() < 8 ) { LOG_DEBUG(GwLog::DEBUG, "failed to parse MWD %s", msg.line); return; } if (msg.FieldLen(0) > 0 && msg.Field(1)[0] == 'T') { WindDirection = formatDegToRad(atof(msg.Field(0))); } if (msg.FieldLen(2) > 0 && msg.Field(3)[0] == 'M') { WindDirectionMagnetic = formatDegToRad(atof(msg.Field(2))); } if (msg.FieldLen(4) > 0 && msg.Field(5)[0] == 'N') { WindSpeed = atof(msg.Field(4)) * knToms; } else if (msg.FieldLen(6) > 0 && msg.Field(7)[0] == 'M') { WindSpeed = atof(msg.Field(6)); } if (WindSpeed == NMEA0183DoubleNA) { logger->logDebug(GwLog::DEBUG, "no wind speed in MWD %s", msg.line); return; } tN2kMsg n2kMsg; bool shouldSend = false; if (WindDirection != NMEA0183DoubleNA){ shouldSend = updateDouble(boatData->TWD, WindDirection, msg.sourceId) && updateDouble(boatData->TWS, WindSpeed, msg.sourceId); if (WindSpeed != NMEA0183DoubleNA) boatData->MaxTws->updateMax(WindSpeed,msg.sourceId); if(shouldSend && boatData->HDT->isValid()) { double twa = WindDirection-boatData->HDT->getData(); if(twa<0) { twa+=2*M_PI; } updateDouble(boatData->TWA, twa, msg.sourceId); const GwConverterConfig::WindMapping mapping=config.findWindMapping(GwConverterConfig::WindMapping::TWA_TWS); if (mapping.valid){ SetN2kWindSpeed(n2kMsg, 1, WindSpeed, twa, mapping.n2kType); send(n2kMsg,msg.sourceId,String(n2kMsg.PGN)+String((int)mapping.n2kType)); } const GwConverterConfig::WindMapping mapping2=config.findWindMapping(GwConverterConfig::WindMapping::TWD_TWS); if (mapping2.valid){ SetN2kWindSpeed(n2kMsg, 1, WindSpeed, WindDirection, mapping2.n2kType); send(n2kMsg,msg.sourceId,String(n2kMsg.PGN)+String((int)mapping2.n2kType)); } } } } void convertHDM(const SNMEA0183Msg &msg){ double HDM=NMEA0183DoubleNA; if (!NMEA0183ParseHDM_nc(msg, HDM)) { LOG_DEBUG(GwLog::DEBUG, "failed to parse HDM %s", msg.line); return; } if (! UD(HDM)) return; tN2kMsg n2kMsg; SetN2kMagneticHeading(n2kMsg,1,HDM, boatData->VAR->getDataWithDefault(N2kDoubleNA), boatData->DEV->getDataWithDefault(N2kDoubleNA) ); send(n2kMsg,msg.sourceId,"127250M"); } void convertHDT(const SNMEA0183Msg &msg){ double HDT=NMEA0183DoubleNA; if (!NMEA0183ParseHDT_nc(msg, HDT)) { LOG_DEBUG(GwLog::DEBUG, "failed to parse HDT %s", msg.line); return; } if (! UD(HDT)) return; tN2kMsg n2kMsg; SetN2kTrueHeading(n2kMsg,1,HDT); send(n2kMsg,msg.sourceId); } void convertHDG(const SNMEA0183Msg &msg){ double HDM=NMEA0183DoubleNA; double DEV=NMEA0183DoubleNA; double VAR=NMEA0183DoubleNA; if (msg.FieldCount() < 5) { LOG_DEBUG(GwLog::DEBUG, "failed to parse HDG %s", msg.line); return; } if (msg.FieldLen(0)>0){ HDM=formatDegToRad(atof(msg.Field(0))); } else{ return; } if (msg.FieldLen(1)>0){ DEV=formatDegToRad(atof(msg.Field(1))); if (msg.Field(2)[0] == 'W') DEV=-DEV; } if (msg.FieldLen(3)>0){ VAR=formatDegToRad(atof(msg.Field(3))); if (msg.Field(4)[0] == 'W') VAR=-VAR; } if (! UD(HDM)) return; UD(VAR); UD(DEV); tN2kMsg n2kMsg; SetN2kMagneticHeading(n2kMsg,1,HDM,DEV,VAR); send(n2kMsg,msg.sourceId,"127250M"); } void convertDPT(const SNMEA0183Msg &msg){ double DepthBelowTransducer=NMEA0183DoubleNA; double Offset=NMEA0183DoubleNA; if (msg.FieldCount() < 2) { LOG_DEBUG(GwLog::DEBUG, "failed to parse DPT %s", msg.line); return; } if (msg.FieldLen(0)>0){ DepthBelowTransducer=atof(msg.Field(0)); } else{ return; } if (msg.FieldLen(1)>0){ Offset=atof(msg.Field(1)); } //offset == 0? SK does not allow this if (Offset != NMEA0183DoubleNA && Offset>=0 ){ if (! boatData->DBS->update(DepthBelowTransducer+Offset,msg.sourceId)) return; } if (Offset == NMEA0183DoubleNA) Offset=N2kDoubleNA; if (! boatData->DBT->update(DepthBelowTransducer,msg.sourceId)) return; tN2kMsg n2kMsg; SetN2kWaterDepth(n2kMsg,1,DepthBelowTransducer,Offset); send(n2kMsg,msg.sourceId,String(n2kMsg.PGN)+String((Offset != N2kDoubleNA)?1:0)); } typedef enum { DBS, DBK, DBT } DepthType; void convertDBKx(const SNMEA0183Msg &msg,DepthType dt){ double Depth=NMEA0183DoubleNA; if (msg.FieldCount() < 6) { LOG_DEBUG(GwLog::DEBUG, "failed to parse DBK/DBS %s", msg.line); return; } for (int i=0;i< 3;i++){ if (msg.FieldLen(0)>0){ Depth=atof(msg.Field(0)); char dt=msg.Field(i+1)[0]; switch(dt){ case 'f': Depth=Depth/mToFeet; break; case 'M': break; case 'F': Depth=Depth/mToFathoms; break; default: //unknown type, try next continue; } if (dt == DBT){ if (! boatData->DBT->update(Depth,msg.sourceId)) return; tN2kMsg n2kMsg; SetN2kWaterDepth(n2kMsg,1,Depth,N2kDoubleNA); send(n2kMsg,msg.sourceId,String(n2kMsg.PGN)+String(0)); return; } //we can only send if we have a valid depth beloww tranducer //to compute the offset if (! boatData->DBT->isValid()) return; double offset=Depth-boatData->DBT->getData(); if (offset >= 0 && dt == DBT){ logger->logDebug(GwLog::DEBUG, "strange DBK - more depth then transducer %s", msg.line); return; } if (offset < 0 && dt == DBS){ logger->logDebug(GwLog::DEBUG, "strange DBS - less depth then transducer %s", msg.line); return; } if (dt == DBS){ if (! boatData->DBS->update(Depth,msg.sourceId)) return; } tN2kMsg n2kMsg; SetN2kWaterDepth(n2kMsg,1,Depth,offset); send(n2kMsg,msg.sourceId,(n2kMsg.PGN)+String((offset != N2kDoubleNA)?1:0)); } } } void convertDBK(const SNMEA0183Msg &msg){ return convertDBKx(msg,DBK); } void convertDBS(const SNMEA0183Msg &msg){ return convertDBKx(msg,DBS); } void convertDBT(const SNMEA0183Msg &msg){ return convertDBKx(msg,DBT); } #define validInstance(name) (name >= 0 && name <= 253) void convertRSA(const SNMEA0183Msg &msg){ double RPOS=NMEA0183DoubleNA; double PRPOS=NMEA0183DoubleNA; if (msg.FieldCount() < 4) { LOG_DEBUG(GwLog::DEBUG, "failed to parse RSA %s", msg.line); return; } tN2kMsg n2kMsg; if (msg.FieldLen(0)>0){ if (msg.Field(1)[0] == 'A'){ RPOS=degToRad*atof(msg.Field(0)); if (UD(RPOS) && validInstance(config.starboardRudderInstance)) { SetN2kRudder(n2kMsg,RPOS,config.starboardRudderInstance); send(n2kMsg,msg.sourceId,"127245S"); } } } if (msg.FieldLen(2)>0){ if (msg.Field(3)[0] == 'A'){ PRPOS=degToRad*atof(msg.Field(2)); if (UD(PRPOS) && validInstance(config.portRudderInstance)){ SetN2kRudder(n2kMsg,PRPOS,config.portRudderInstance); send(n2kMsg,msg.sourceId,"127245P"); } } } } void convertVHW(const SNMEA0183Msg &msg){ double TrueHeading=NMEA0183DoubleNA; double MagneticHeading=NMEA0183DoubleNA; double STW=NMEA0183DoubleNA; if (! NMEA0183ParseVHW_nc(msg,TrueHeading,MagneticHeading,STW)){ LOG_DEBUG(GwLog::DEBUG, "failed to parse VHW %s", msg.line); return; } tN2kMsg n2kMsg; if (updateDouble(boatData->HDT,TrueHeading,msg.sourceId)){ SetN2kTrueHeading(n2kMsg,1,TrueHeading); send(n2kMsg,msg.sourceId); } if(updateDouble(boatData->HDM,MagneticHeading,msg.sourceId)){ SetN2kMagneticHeading(n2kMsg,1,MagneticHeading, boatData->DEV->getDataWithDefault(N2kDoubleNA), boatData->VAR->getDataWithDefault(N2kDoubleNA) ); send(n2kMsg,msg.sourceId,"127250M"); //ensure both mag and true are sent } if (! updateDouble(boatData->STW,STW,msg.sourceId)) return; SetN2kBoatSpeed(n2kMsg,1,STW); send(n2kMsg,msg.sourceId); } void convertVTG(const SNMEA0183Msg &msg){ double COG=NMEA0183DoubleNA; double SOG=NMEA0183DoubleNA; double MCOG=NMEA0183DoubleNA; if (! NMEA0183ParseVTG_nc(msg,COG,MCOG,SOG)){ LOG_DEBUG(GwLog::DEBUG, "failed to parse VTG %s", msg.line); return; } if (! UD(COG)) return; if (! UD(SOG)) return; tN2kMsg n2kMsg; //TODO: maybe use MCOG if no COG? SetN2kCOGSOGRapid(n2kMsg,1,N2khr_true,COG,SOG); send(n2kMsg,msg.sourceId); } void convertZDA(const SNMEA0183Msg &msg){ time_t DateTime; long Timezone; if (! NMEA0183ParseZDA(msg,DateTime,Timezone)){ LOG_DEBUG(GwLog::DEBUG, "failed to parse ZDA %s", msg.line); return; } uint32_t DaysSince1970=tNMEA0183Msg::elapsedDaysSince1970(DateTime); tmElements_t parts; tNMEA0183Msg::breakTime(DateTime,parts); double GpsTime=parts.tm_sec+60*parts.tm_min+3600*parts.tm_hour; if (! boatData->GPSD->update(DaysSince1970,msg.sourceId)) return; if (! boatData->GPST->update(GpsTime,msg.sourceId)) return; bool timezoneValid=false; if (msg.FieldLen(4) > 0 && msg.FieldLen(5)>0){ Timezone=Timezone/60; //N2K has offset in minutes if (! boatData->TZ->update(Timezone,msg.sourceId)) return; timezoneValid=true; } tN2kMsg n2kMsg; if (timezoneValid){ SetN2kLocalOffset(n2kMsg,DaysSince1970,GpsTime,Timezone); send(n2kMsg,msg.sourceId); } SetN2kSystemTime(n2kMsg,1,DaysSince1970,GpsTime); send(n2kMsg,msg.sourceId); } void convertGGA(const SNMEA0183Msg &msg){ double GPSTime=NMEA0183DoubleNA; double Latitude=NMEA0183DoubleNA; double Longitude=NMEA0183DoubleNA; int GPSQualityIndicator=NMEA0183Int32NA; int SatelliteCount=NMEA0183Int32NA; double HDOP=NMEA0183DoubleNA; double Altitude=NMEA0183DoubleNA; double GeoidalSeparation=NMEA0183DoubleNA; double DGPSAge=NMEA0183DoubleNA; int DGPSReferenceStationID=NMEA0183Int32NA; if (! NMEA0183ParseGGA_nc(msg,GPSTime, Latitude,Longitude, GPSQualityIndicator, SatelliteCount, HDOP, Altitude,GeoidalSeparation, DGPSAge, DGPSReferenceStationID)){ LOG_DEBUG(GwLog::DEBUG, "failed to parse GGA %s", msg.line); return; } if (GPSQualityIndicator == 0){ LOG_DEBUG(GwLog::DEBUG, "quality 0 (no fix) for GGA %s", msg.line); return; } if (! updateDouble(boatData->GPST,GPSTime,msg.sourceId)) return; if (! updateDouble(boatData->LAT,Latitude,msg.sourceId)) return; if (! updateDouble(boatData->LON,Longitude,msg.sourceId)) return; if (! updateDouble(boatData->ALT,Altitude,msg.sourceId)) return; if (! updateDouble(boatData->HDOP,HDOP,msg.sourceId)) return; if (! boatData->GPSD->isValid()) return; tN2kMsg n2kMsg; tN2kGNSSmethod method=N2kGNSSm_noGNSS; if (GPSQualityIndicator <=5 ) method= (tN2kGNSSmethod)GPSQualityIndicator; SetN2kGNSS(n2kMsg,1, boatData->GPSD->getData(), GPSTime, Latitude, Longitude, Altitude, N2kGNSSt_GPS, method, SatelliteCount, HDOP, boatData->PDOP->getDataWithDefault(N2kDoubleNA), 0, 0, N2kGNSSt_GPS, DGPSReferenceStationID, DGPSAge); send(n2kMsg,msg.sourceId); } void convertGSA(const SNMEA0183Msg &msg){ if (msg.FieldCount() < 17) { LOG_DEBUG(GwLog::DEBUG, "failed to parse GSA %s", msg.line); return; } int fixMode=atoi(msg.Field(1)); if (fixMode != 2 && fixMode != 3){ LOG_DEBUG(GwLog::DEBUG,"no fix in GSA, mode=%d",fixMode); return; } tN2kMsg n2kMsg; tN2kGNSSDOPmode mode=N2kGNSSdm_Unavailable; if (fixMode >= 0 && fixMode <=3) mode=(tN2kGNSSDOPmode)fixMode; tN2kGNSSDOPmode rmode=mode; if (msg.Field(0)[0] == 'A') rmode=N2kGNSSdm_Auto; double HDOP=N2kDoubleNA; double VDOP=N2kDoubleNA; double PDOP=N2kDoubleNA; if (msg.FieldLen(14)> 0){ PDOP=atof(msg.Field(14)); if (!updateDouble(boatData->PDOP,PDOP,msg.sourceId)) return; } if (msg.FieldLen(15)> 0){ HDOP=atof(msg.Field(15)); if (!updateDouble(boatData->HDOP,HDOP,msg.sourceId)) return; } if (msg.FieldLen(16)> 0){ VDOP=atof(msg.Field(16)); if (!updateDouble(boatData->VDOP,VDOP,msg.sourceId)) return; } SetN2kGNSSDOPData(n2kMsg,1,rmode,mode,HDOP,VDOP,N2kDoubleNA); send(n2kMsg,msg.sourceId); } void convertGSV(const SNMEA0183Msg &msg){ if (msg.FieldCount() < 7){ LOG_DEBUG(GwLog::DEBUG,"unable to parse GSV %s",msg.line); return; } uint32_t total=atoi(msg.Field(0)); if (total < 1 || total > 9) { LOG_DEBUG(GwLog::DEBUG,"GSV invalid total %u %s",total,msg.line); return; } uint32_t current=atoi(msg.Field(1)); if (current < 1 || current > total) { LOG_DEBUG(GwLog::DEBUG,"GSV invalid current %u %s",current,msg.line); return; } for (int idx=3;idx < msg.FieldCount();idx+=4){ if (msg.FieldLen(idx) < 1 || msg.FieldLen(idx+1) < 1 || msg.FieldLen(idx+2) < 1 || msg.FieldLen(idx+3) < 1 ) continue; GwSatInfo info; info.PRN=atoi(msg.Field(idx)); info.Elevation=atoi(msg.Field(idx+1)); info.Azimut=atoi(msg.Field(idx+2)); info.SNR=atoi(msg.Field(idx+3)); if (!boatData->SatInfo->update(info,msg.sourceId)) return; } int numSat=boatData->SatInfo->getNumSats(); if (current == total && numSat > 0){ tN2kMsg n2kMsg; SetN2kGNSSSatellitesInView(n2kMsg,1); bool hasInfos=false; for (int i=0;iSatInfo->getAt(i); if (gwInfo){ hasInfos=true; satInfo.PRN=gwInfo->PRN; satInfo.SNR=gwInfo->SNR; satInfo.Elevation=DegToRad(gwInfo->Elevation); satInfo.Azimuth=DegToRad(gwInfo->Azimut); AppendSatelliteInfo(n2kMsg,satInfo); } } if (hasInfos){ send(n2kMsg,msg.sourceId); } } } void convertGLL(const SNMEA0183Msg &msg){ tGLL GLL; if (! NMEA0183ParseGLL_nc(msg,GLL)){ LOG_DEBUG(GwLog::DEBUG,"unable to parse GLL %s",msg.line); return; } if (GLL.status != 'A') return; if (! updateDouble(boatData->LAT,GLL.latitude,msg.sourceId)) return; if (! updateDouble(boatData->LON,GLL.longitude,msg.sourceId)) return; if (! updateDouble(boatData->GPST,GLL.GPSTime,msg.sourceId)) return; tN2kMsg n2kMsg; SetN2kLatLonRapid(n2kMsg,GLL.latitude,GLL.longitude); send(n2kMsg,msg.sourceId); } void convertROT(const SNMEA0183Msg &msg){ double ROT=NMEA0183DoubleNA; if (! NMEA0183ParseROT_nc(msg,ROT)){ LOG_DEBUG(GwLog::DEBUG,"unable to parse ROT %s",msg.line); return; } ROT=ROT / ROT_WA_FACTOR; if (! updateDouble(boatData->ROT,ROT,msg.sourceId)) return; tN2kMsg n2kMsg; SetN2kRateOfTurn(n2kMsg,1,ROT); send(n2kMsg,msg.sourceId); } void convertXTE(const SNMEA0183Msg &msg){ if (msg.FieldCount() < 6){ LOG_DEBUG(GwLog::DEBUG,"unable to parse XTE %s",msg.line); return; } if (msg.Field(0)[0] != 'A') return; if (msg.Field(1)[0] != 'A') return; if (msg.Field(4)[0] != 'N') return; //nm only if (msg.FieldLen(2) < 1) return; const char dir=msg.Field(3)[0]; if (dir != 'L' && dir != 'R') return; double xte=atof(msg.Field(2)) * nmTom; if (dir == 'R') xte=-xte; if (! updateDouble(boatData->XTE,xte,msg.sourceId)) return; tN2kMsg n2kMsg; tN2kXTEMode mode=xteMode(msg.Field(5)[0]); SetN2kXTE(n2kMsg,1,mode,false,xte); send(n2kMsg,msg.sourceId); } void convertMTW(const SNMEA0183Msg &msg){ if (msg.FieldCount() < 2){ LOG_DEBUG(GwLog::DEBUG,"unable to parse MTW %s",msg.line); return; } if (msg.Field(1)[0] != 'C'){ LOG_DEBUG(GwLog::DEBUG,"invalid temp unit in MTW %s",msg.line); return; } if (msg.FieldLen(0) < 1) return; double WTemp=CToKelvin(atof(msg.Field(0))); UD(WTemp); tN2kMsg n2kMsg; SetN2kPGN130310(n2kMsg,1,WTemp); send(n2kMsg,msg.sourceId); } //shortcut for lambda converters #define CVL [](const SNMEA0183Msg &msg, NMEA0183DataToN2KFunctions *p) -> void void registerConverters() { converters.registerConverter(129283UL,129284UL,129285UL, String(F("RMB")), &NMEA0183DataToN2KFunctions::convertRMB); converters.registerConverter( 126992UL,129025UL,129026UL,127258UL, String(F("RMC")), &NMEA0183DataToN2KFunctions::convertRMC); converters.registerConverter( 130306UL, String(F("MWV")),&NMEA0183DataToN2KFunctions::convertMWV); converters.registerConverter( 130306UL, String(F("MWD")),&NMEA0183DataToN2KFunctions::convertMWD); converters.registerConverter( 130306UL, String(F("VWR")),&NMEA0183DataToN2KFunctions::convertVWR); converters.registerConverter( 127250UL, String(F("HDM")),&NMEA0183DataToN2KFunctions::convertHDM); converters.registerConverter( 127250UL, String(F("HDT")),&NMEA0183DataToN2KFunctions::convertHDT); converters.registerConverter( 127250UL, String(F("HDG")),&NMEA0183DataToN2KFunctions::convertHDG); converters.registerConverter( 128267UL, String(F("DPT")), &NMEA0183DataToN2KFunctions::convertDPT); converters.registerConverter( 128267UL, String(F("DBK")), &NMEA0183DataToN2KFunctions::convertDBK); converters.registerConverter( 128267UL, String(F("DBS")), &NMEA0183DataToN2KFunctions::convertDBS); converters.registerConverter( 128267UL, String(F("DBT")), &NMEA0183DataToN2KFunctions::convertDBT); converters.registerConverter( 127245UL, String(F("RSA")), &NMEA0183DataToN2KFunctions::convertRSA); converters.registerConverter( 128259UL, String(F("VHW")), &NMEA0183DataToN2KFunctions::convertVHW); converters.registerConverter( 129026UL, String(F("VTG")), &NMEA0183DataToN2KFunctions::convertVTG); converters.registerConverter( 129033UL,126992UL, String(F("ZDA")), &NMEA0183DataToN2KFunctions::convertZDA); converters.registerConverter( 129029UL, String(F("GGA")), &NMEA0183DataToN2KFunctions::convertGGA); converters.registerConverter( 129539UL, String(F("GSA")), &NMEA0183DataToN2KFunctions::convertGSA); converters.registerConverter( 129540UL, String(F("GSV")), &NMEA0183DataToN2KFunctions::convertGSV); converters.registerConverter( 129025UL, String(F("GLL")), &NMEA0183DataToN2KFunctions::convertGLL); converters.registerConverter( 127251UL, String(F("ROT")), &NMEA0183DataToN2KFunctions::convertROT); converters.registerConverter( 129283UL, String(F("XTE")), &NMEA0183DataToN2KFunctions::convertXTE); converters.registerConverter( 130310UL, String(F("MTW")), &NMEA0183DataToN2KFunctions::convertMTW); unsigned long *xdrpgns=new unsigned long[8]{127505UL,127508UL,130312UL,130313UL,130314UL,127489UL,127488UL,127257UL}; converters.registerConverter( 8, xdrpgns, F("XDR"), &NMEA0183DataToN2KFunctions::convertXDR); unsigned long *aispgns=new unsigned long[7]{129810UL,129809UL,129040UL,129039UL,129802UL,129794UL,129038UL}; converters.registerConverter(7,&aispgns[0], String(F("AIVDM")),&NMEA0183DataToN2KFunctions::convertAIVDX); converters.registerConverter(7,&aispgns[0], String(F("AIVDO")),&NMEA0183DataToN2KFunctions::convertAIVDX); } public: virtual bool parseAndSend(const char *buffer, int sourceId) { LOG_DEBUG(GwLog::DEBUG + 1, "NMEA0183DataToN2K[%d] parsing %s", sourceId, buffer) SNMEA0183Msg msg(buffer,sourceId); if (! msg.isAis){ if (!msg.SetMessageCor(buffer)) { LOG_DEBUG(GwLog::DEBUG, "NMEA0183DataToN2K[%d] invalid message %s", sourceId, buffer) return false; } } String code = msg.getKey(); bool rt = converters.handleMessage(code, msg, this); if (!rt) { LOG_DEBUG(GwLog::DEBUG, "NMEA0183DataToN2K[%d] no handler for (%s) %s", sourceId, code.c_str(), buffer); } else{ LOG_DEBUG(GwLog::DEBUG+1, "NMEA0183DataToN2K[%d] handler done ", sourceId); } return rt; } virtual unsigned long *handledPgns() { return converters.handledPgns(); } virtual int numConverters(){ return converters.numConverters(); } virtual String handledKeys(){ return converters.handledKeys(); } NMEA0183DataToN2KFunctions(GwLog *logger, GwBoatData *boatData, N2kSender callback, GwXDRMappings *xdrMappings, const GwConverterConfig &cfg) : NMEA0183DataToN2K(logger, boatData, callback) { this->config=cfg; this->xdrMappings=xdrMappings; aisDecoder= new MyAisDecoder(logger,this->sender); registerConverters(); LOG_DEBUG(GwLog::LOG, "NMEA0183DataToN2KFunctions: registered %d converters", converters.numConverters()); } }; NMEA0183DataToN2K* NMEA0183DataToN2K::create(GwLog *logger,GwBoatData *boatData,N2kSender callback, GwXDRMappings *xdrMappings, const GwConverterConfig &config){ return new NMEA0183DataToN2KFunctions(logger, boatData,callback,xdrMappings,config); }