From 89f7420204456c9be90fbc8e421ba61bc3f88380 Mon Sep 17 00:00:00 2001 From: quantenschaum Date: Mon, 1 Jul 2024 14:24:17 +0200 Subject: [PATCH 1/8] fixed handling of missing values --- server/avnav_nmea.py | 216 ++++++++++++++++++++--------------------- viewer/map/navlayer.js | 12 +-- 2 files changed, 109 insertions(+), 119 deletions(-) diff --git a/server/avnav_nmea.py b/server/avnav_nmea.py index 73877612a..56b228874 100644 --- a/server/avnav_nmea.py +++ b/server/avnav_nmea.py @@ -22,7 +22,7 @@ # DEALINGS IN THE SOFTWARE. # # parts from this software (AIS decoding) are taken from the gpsd project -# so refer to this BSD licencse also (see ais.py) or omit ais.py +# so refer to this BSD licencse also (see ais.py) or omit ais.py ############################################################################### from avnav_store import * @@ -64,18 +64,18 @@ class NMEAParser(object): K_HDGC=Key('headingCompass','compass heading','\N{DEGREE SIGN}','navigation.headingCompass',signalKConversion=AVNUtil.rad2deg) K_HDGM=Key('headingMag','magnetic heading','\N{DEGREE SIGN}','navigation.headingMagnetic',signalKConversion=AVNUtil.rad2deg) K_HDGT=Key('headingTrue','true heading','\N{DEGREE SIGN}','navigation.headingTrue',signalKConversion=AVNUtil.rad2deg) - K_VWTT=Key('waterTemp','water temparature','k',signalK='environment.water.temperature') + K_VWTT=Key('waterTemp','water temperature','k',signalK='environment.water.temperature') K_VHWS=Key('waterSpeed','speed through water','m/s','navigation.speedThroughWater') K_TWS=Key('trueWindSpeed','wind speed true (speed through water ref or ground ref)','m/s','environment.wind.speedTrue') K_AWS=Key('windSpeed','apparent wind speed in m/s','m/s','environment.wind.speedApparent') K_TWA=Key('trueWindAngle','true wind angle','\N{DEGREE SIGN}','environment.wind.angleTrueWater',signalKConversion=AVNUtil.rad2deg) K_TWD=Key('trueWindDirection','true wind direction','\N{DEGREE SIGN}') - K_AWA=Key('windAngle','wind direction','\N{DEGREE SIGN}','environment.wind.angleApparent',signalKConversion=AVNUtil.rad2deg) + K_AWA=Key('windAngle','apparent wind angle','\N{DEGREE SIGN}','environment.wind.angleApparent',signalKConversion=AVNUtil.rad2deg) K_MDEV=Key('magDeviation', 'magnetic Deviation in deg','\N{DEGREE SIGN}', signalK='navigation.magneticDeviation', signalKConversion=AVNUtil.rad2deg) K_MVAR=Key('magVariation', 'magnetic Variation in deg','\N{DEGREE SIGN}', signalK='navigation.magneticVariation', signalKConversion=AVNUtil.rad2deg) K_LAT=Key('lat','gps latitude',signalK='navigation.position.latitude') K_LON=Key('lon','gps longitude',signalK='navigation.position.longitude') - K_COG=Key('track','course','\N{DEGREE SIGN}','navigation.courseOverGroundTrue',signalKConversion=AVNUtil.rad2deg) + K_COG=Key('track','course over ground','\N{DEGREE SIGN}','navigation.courseOverGroundTrue',signalKConversion=AVNUtil.rad2deg) K_SOG=Key('speed','speed in m/s','m/s','navigation.speedOverGround') K_SET=Key('currentSet','current set','\N{DEGREE SIGN}','environment.current.setTrue',signalKConversion=AVNUtil.rad2deg) K_DFT=Key('currentDrift','current drift in m/s','m/s','environment.current.drift') @@ -121,7 +121,7 @@ def registerKeys(cls,navdata): #we use the PRN as additional key behind for satkey in cls.SKY_SATELLITE_KEYS: navdata.registerKey(AVNStore.BASE_KEY_SKY+".satellites.*."+satkey,{'description':'sat status entry'},cls.__name__) - + def __init__(self,navdata): self.payloads = {'A':'', 'B':''} #AIS paylod data self.navdata=navdata # type: AVNStore @@ -140,12 +140,12 @@ def formatTime(cls,ts): def addToNavData(self,data,record=None,source='internal',priority=0,timestamp=None): ''' add a data dictionary toe the internal store - :param data: - :param record: - :param source: - :param priority: + :param data: + :param record: + :param source: + :param priority: :param timestamp: steady time point - :return: + :return: ''' self.navdata.setValue(AVNStore.BASE_KEY_GPS,data,source=source,priority=priority,record=record,timestamp=timestamp) @@ -189,7 +189,7 @@ def gpsTimeToTime(cls,gpstime,gpsdate=None): gpsdt=datetime.datetime.combine(date,gpsts) AVNLog.ld("gpsts computed",gpsdt) return gpsdt - + #parse the nmea psoition fields: #gggmm.dec,N - 1-3 characters grad, mm 2 didgits minutes #direction N,S,E,W - S,W being negative @@ -284,7 +284,7 @@ def nmeaChecksum(cls,part): #return ("%X"%chksum).zfill(2) return ("%02X"%chksum) - #parse a line of NMEA data and store it in the navdata array + #parse a line of NMEA data and store it in the navdata array def parseData(self,data,source='internal',sourcePriority=DEFAULT_SOURCE_PRIORITY,timestamp=None): basePriority=sourcePriority*10 valAndSum=data.rstrip().split("*") @@ -303,68 +303,65 @@ def parseData(self,data,source='internal',sourcePriority=DEFAULT_SOURCE_PRIORITY return False AVNLog.debug("parse AIS data %s",data) return self.ais_packet_scanner(data,source=source,sourcePriority=sourcePriority,timestamp=timestamp) - + tag=darray[0][3:] rt={} #currently we only take the time from RMC #as only with this one we have really a valid complete timestamp try: if tag=='GGA': - mode=int(darray[6] or '0') #quality - if mode >= 1: + mode=int(darray[6] or 0) #quality + if mode >= 1 and all(darray[i] for i in (2,3,4,5)): rt[self.K_LAT.key]=self.nmeaPosToFloat(darray[2],darray[3]) rt[self.K_LON.key]=self.nmeaPosToFloat(darray[4],darray[5]) - rt['satUsed']=int(darray[7] or '0') + if darray[7]: + rt['satUsed']=int(darray[7]) self.addToNavData(rt,source=source,record=tag,timestamp=timestamp) return True if tag=='GSV': - rt['satInview']=int(darray[3] or '0') + if darray[3]: + rt['satInview']=int(darray[3]) + else: + return False self.addToNavData(rt,source=source,record=tag,priority=basePriority,timestamp=timestamp) return True if tag=='GLL': - mode=1 - if len(darray) > 6: - mode= (0 if (darray[6] != 'A') else 2) - if mode >= 1: + valid = len(darray)<=6 or darray[6]=='A' + if valid and all(darray[i] for i in (1,2,3,4)): rt[self.K_LAT.key]=self.nmeaPosToFloat(darray[1],darray[2]) rt[self.K_LON.key]=self.nmeaPosToFloat(darray[3],darray[4]) - self.addToNavData(rt,source=source,record=tag,priority=basePriority,timestamp=timestamp) + else: + return False + self.addToNavData(rt,source=source,record=tag,priority=basePriority,timestamp=timestamp) return True if tag=='VTG': - mode=darray[2] - rt[self.K_COG.key]=float(darray[1] or '0') - if (mode == 'T'): - #new mode - rt[self.K_SOG.key]=float(darray[5] or '0')*self.NM/3600 - else: - rt[self.K_SOG.key]=float(darray[3]or '0')*self.NM/3600 + if darray[1]: + rt[self.K_COG.key]=float(darray[1]) + i = 5 if darray[2]=='T' else 3 + if darray[i]: + rt[self.K_SOG.key]=float(darray[i])*self.NM/3600 self.addToNavData(rt,source=source,record=tag,priority=basePriority,timestamp=timestamp) return True if tag=='RMC': #$--RMC,hhmmss.ss,A,llll.ll,a,yyyyy.yy,a,x.x,x.x,xxxx,x.x,a*hh #this includes current date - mode=( 0 if darray[2] != 'A' else 2) - if mode >= 1: + valid = darray[2]=='A' + if valid and all(darray[i] for i in (3,4,5,6)): rt[self.K_LAT.key]=self.nmeaPosToFloat(darray[3],darray[4]) rt[self.K_LON.key]=self.nmeaPosToFloat(darray[5],darray[6]) - if darray[7] != '': - rt[self.K_SOG.key]=float(darray[7] or '0')*self.NM/3600 - if darray[8] != '': - rt[self.K_COG.key]=float(darray[8] or '0') - gpstime = darray[1] - gpsdate = darray[9] - if darray[10] != '': - if darray[11] == 'E': - rt[self.K_MVAR.key] = float(darray[10] or '0') - elif darray[11] == 'W': - rt[self.K_MVAR.key] = -float(darray[10] or '0') - if gpsdate != "" and gpstime != "": - rt['time']=self.formatTime(self.gpsTimeToTime(gpstime, gpsdate)) + if valid: + if darray[7]: + rt[self.K_SOG.key]=float(darray[7])*self.NM/3600 + if darray[8]: + rt[self.K_COG.key]=float(darray[8]) + if darray[10] and darray[11]: + rt[self.K_MVAR.key] = float(darray[10]) * (-1 if darray[11] == 'W' else +1) + if darray[1] and darray[9]: + rt['time']=self.formatTime(self.gpsTimeToTime(darray[1], darray[9])) self.addToNavData(rt,source=source,priority=basePriority+1,record=tag,timestamp=timestamp) return True if tag == 'ZDA': - if darray[1] == '' or darray[2] == '' or darray[3] == '' or darray[4] == '': - return False + if not all(darray[i] for i in (1,2,3,4)): return False gpstime=darray[1] #ensure each 2 digits for day and month gpsdate=('0' + darray[2])[-2:] +('0' + darray[3])[-2:]+('0000'+darray[4])[-4:] @@ -388,23 +385,20 @@ def parseData(self,data,source='internal',sourcePriority=DEFAULT_SOURCE_PRIORITY 8 K = Kilometers Per Hour Checksum ''' - windAngle=float(darray[1] or '0') - dir=darray[2] - rt[self.K_AWA.key]= 360-windAngle if ( dir == 'L' or dir == 'l') else windAngle - priority=0 - #we keep the speed im m/s + if darray[1] and darray[2]: + windAngle, dir = float(darray[1]), darray[2].upper() + rt[self.K_AWA.key] = 360-windAngle if dir == 'L' else windAngle + # we keep the speed im m/s windspeed=None - if darray[3] != '': - windspeed=float(darray[3] or '0') - windspeed=windspeed*self.NM/3600 - elif darray[5] != '': - windspeed=float(darray[5] or '0') - windspeed=windspeed/3.6 - elif darray[7] != '': - windspeed=float(darray[7] or '0') + if darray[3]: + windspeed=float(darray[3])*self.NM/3600 + elif darray[5]: + windspeed=float(darray[5])/3.6 + elif darray[7]: + windspeed=float(darray[7]) if windspeed is not None: rt[self.K_AWS.key]=windspeed - self.addToNavData(rt,source=source,record=tag,priority=basePriority+priority,timestamp=timestamp) + self.addToNavData(rt,source=source,record=tag,priority=basePriority,timestamp=timestamp) return True if tag == 'MWV': ''' @@ -418,16 +412,19 @@ def parseData(self,data,source='internal',sourcePriority=DEFAULT_SOURCE_PRIORITY 6) Checksum ''' ref=darray[2] + if not ref: return False angleKey=self.K_TWA if ref == 'T' else self.K_AWA speedKey=self.K_TWS if ref == 'T' else self.K_AWS - rt[angleKey.key]=float(darray[1]) + if darray[1]: + rt[angleKey.key]=float(darray[1]) #we keep the speed im m/s - windspeed=float(darray[3] or '0') - if (darray[4] == 'K'): - windspeed=windspeed/3.6 - if (darray[4] == 'N'): - windspeed=windspeed*self.NM/3600 - rt[speedKey.key]=windspeed + if darray[3]: + windspeed=float(darray[3]) + if (darray[4] == 'K'): + windspeed=windspeed/3.6 + if (darray[4] == 'N'): + windspeed=windspeed*self.NM/3600 + rt[speedKey.key]=windspeed self.addToNavData(rt,source=source,record=tag,priority=basePriority,timestamp=timestamp) return True if tag == 'MWD': @@ -436,18 +433,18 @@ def parseData(self,data,source='internal',sourcePriority=DEFAULT_SOURCE_PRIORITY $WIMWD,10.1,T,10.1,M,12,N,40,M*5D https://github.com/adrianmo/go-nmea/blob/master/mwd.go ''' - if darray[1] != '': + if darray[1]: rt[self.K_TWD.key]=float(darray[1]) hasData=True else: - if darray[3] != '': + if darray[3]: rt[self.K_TWD.key] = float(darray[3]) hasData=True - if ( darray[8] == 'M' or darray[8] == 'm') and darray[7] != '': + if darray[8].upper() == 'M' and darray[7]: rt[self.K_TWS.key]=float(darray[7]) hasData=True else: - if (darray[6] == 'N' or darray[6] == 'n') and darray[5] != '': + if darray[6].upper() == 'N' and darray[5]: rt[self.K_TWS.key] = float(darray[5]) * self.NM/3600.0 hasData=True if hasData: @@ -466,14 +463,14 @@ def parseData(self,data,source='internal',sourcePriority=DEFAULT_SOURCE_PRIORITY negative means distance from transducer to keel 3) Checksum ''' - rt[self.K_DEPTHT.key] = float(darray[1] or '0') - if len(darray[2]) > 0: - if float(darray[2]) >= 0: - rt[self.K_DEPTHW.key] = float(darray[1] or '0') + float(darray[2] or '0') - else: - rt[self.K_DEPTHK.key] = float(darray[1] or '0') + float(darray[2] or '0') - self.addToNavData(rt,source=source,record=tag,priority=basePriority,timestamp=timestamp) - return True + if darray[1]: + dpt = rt[self.K_DEPTHT.key] = float(darray[1]) + if darray[2]: + offset = float(darray[2]) + rt[self.K_DEPTHW.key if offset >= 0 else self.K_DEPTHK.key] = dpt + offset + self.addToNavData(rt,source=source,record=tag,priority=basePriority,timestamp=timestamp) + return True + return False if tag == 'DBT': ''' DBT - Depth below transducer @@ -489,8 +486,8 @@ def parseData(self,data,source='internal',sourcePriority=DEFAULT_SOURCE_PRIORITY 6) F = Fathoms 7) Checksum ''' - if len(darray[3]) > 0: - rt[self.K_DEPTHT.key] = float(darray[3] or '0') + if darray[3]: + rt[self.K_DEPTHT.key] = float(darray[3]) self.addToNavData(rt,source=source,record=tag,priority=basePriority,timestamp=timestamp) return True return False @@ -515,17 +512,13 @@ def parseData(self,data,source='internal',sourcePriority=DEFAULT_SOURCE_PRIORITY MagDeviation=0 MagVarDir=None MagVariation=None - if(len(darray[1]) > 0): - heading_c = float(darray[1] or '0') + if darray[1]: + heading_c = float(darray[1]) rt[self.K_HDGC.key] = heading_c - if(len(darray[2]) > 0): - MagDeviation = float(darray[2] or '0') # --> Ablenkung - if(len(darray[3]) > 0): - MagDevDir = darray[3] or 'X' - if(len(darray[4]) > 0): - MagVariation = float(darray[4] or '0') # --> Missweisung - if(len(darray[5]) > 0): - MagVarDir = darray[5] or 'X' + if darray[2] and darray[3]: + MagDeviation,MagDevDir = float(darray[2]),darray[3] # --> Ablenkung + if darray[4] and darray[5]: + MagVariation,MagVarDir = float(darray[4]),darray[5] # --> Missweisung # Deviation heading_m = heading_c if MagDevDir == 'E': @@ -552,8 +545,8 @@ def parseData(self,data,source='internal',sourcePriority=DEFAULT_SOURCE_PRIORITY if tag == 'HDM' or tag == 'HDT': heading=None - if len(darray[1]) > 0: - heading = float(darray[1] or '0') + if darray[1]: + heading = float(darray[1]) magortrue = darray[2] if heading is not None: if magortrue == 'T': @@ -580,7 +573,7 @@ def parseData(self,data,source='internal',sourcePriority=DEFAULT_SOURCE_PRIORITY rt[self.K_DFT.key] = float(darray[5]) self.addToNavData(rt,source=source,record=tag,priority=basePriority,timestamp=timestamp) return True - + #VHW - Water speed and heading # 1 2 3 4 5 6 7 8 9 @@ -600,12 +593,12 @@ def parseData(self,data,source='internal',sourcePriority=DEFAULT_SOURCE_PRIORITY if tag == 'VHW': - if len(darray[1]) > 0: # Heading True - rt[self.K_HDGT.key] = float(darray[1] or '0') - if(len(darray[3]) > 0): - rt[self.K_HDGM.key] = float(darray[3] or '0') # Heading magnetic - if len(darray[5]) > 0: - rt[self.K_VHWS.key]= float(darray[5] or '0')*self.NM/3600 + if darray[1]: # Heading True + rt[self.K_HDGT.key] = float(darray[1]) + if darray[3]: + rt[self.K_HDGM.key] = float(darray[3]) # Heading magnetic + if darray[5]: + rt[self.K_VHWS.key]= float(darray[5])*self.NM/3600 self.addToNavData(rt,source=source,record=tag,priority=basePriority,timestamp=timestamp) return True @@ -624,15 +617,14 @@ def parseData(self,data,source='internal',sourcePriority=DEFAULT_SOURCE_PRIORITY while i < (lf -3): try: # we need 4 fields - if darray[i + 1] is not None and darray[i] != "": + if all(darray[i + k] for k in range(4)): ttype = darray[i] - tdata = float(darray[i + 1] or '0') + tdata = float(darray[i + 1]) tunit = darray[i + 2] tname = darray[i + 3] data=self.convertXdrValue(tdata,tunit) - if tname is not None and tname != "": - rt["transducers."+tname]=data - hasData=True + rt["transducers."+tname]=data + hasData=True except Exception as e: AVNLog.debug("decode %s at pos %d failed: %s"%(data,i,str(e))) pass @@ -664,8 +656,8 @@ def convertXdrValue(self, value, unit): value=value*100*1000 return value - #parse one line of AIS data - #taken from ais.py and adapted to our input handling + #parse one line of AIS data + #taken from ais.py and adapted to our input handling def ais_packet_scanner(self,line,source='internal',sourcePriority=DEFAULT_SOURCE_PRIORITY,timestamp=None): basePriority=sourcePriority*10 "Get a span of AIVDM packets with contiguous fragment numbers." @@ -673,7 +665,7 @@ def ais_packet_scanner(self,line,source='internal',sourcePriority=DEFAULT_SOURCE AVNLog.debug("ignore unknown AIS data %s",line) return False line = line.strip() - # Strip off USCG metadata + # Strip off USCG metadata line = re.sub(r"(?<=\*[0-9A-F][0-9A-F]),.*", "", line) # Compute CRC-16 checksum packet = line[1:-3] # Strip leading !, trailing * and CRC @@ -696,7 +688,7 @@ def ais_packet_scanner(self,line,source='internal',sourcePriority=DEFAULT_SOURCE self.payloads[channel] += fields[5] try: # This works because a mangled pad literal means - # a malformed packet that will be caught by the CRC check. + # a malformed packet that will be caught by the CRC check. pad = int(fields[6].split('*')[0]) except ValueError: pad = 0 @@ -740,7 +732,7 @@ def parse_ais_messages(self,raw,bits,source='internal',priority=0,timestamp=None # Apply the postprocessor stage cooked = ais.postprocess(cooked) expected = ais.lengths.get(values['msgtype'], None) - # Check length; has to be done after so we have the type field + # Check length; has to be done after so we have the type field bogon = False if expected is not None: if type(expected) == type(0): @@ -757,7 +749,7 @@ def parse_ais_messages(self,raw,bits,source='internal',priority=0,timestamp=None except: AVNLog.debug("exception %s while decoding AIS data %s",traceback.format_exc()) return False - + def storeAISdata(self,bitfield,source='internal',priority=0,timestamp=None): rt={'class':'AIS'} storeData=False @@ -776,4 +768,4 @@ def storeAISdata(self,bitfield,source='internal',priority=0,timestamp=None): AVNLog.debug("ignoring AIS data without mmsi, %s"%rt) return self.navdata.setAisValue(mmsi,AVNUtil.convertAIS(rt),source=source,priority=priority,timestamp=timestamp) - + diff --git a/viewer/map/navlayer.js b/viewer/map/navlayer.js index 2395bb850..b13df1d5c 100644 --- a/viewer/map/navlayer.js +++ b/viewer/map/navlayer.js @@ -162,8 +162,6 @@ NavLayer.prototype.onPostCompose=function(center,drawing){ let boatRotation=gps.boatDirection; let useHdg=gps.directionMode !== 'cog'; let boatStyle=assign({},gps.isSteady?this.boatStyleSteady:(useHdg?this.boatStyleHdg:this.boatStyle)); - let course=gps.course; - if (course === undefined) course=0; if (boatStyle.rotate === false){ boatStyle.rotation=0; } @@ -189,9 +187,9 @@ NavLayer.prototype.onPostCompose=function(center,drawing){ courseVectorStyle.color = boatStyle.courseVectorColor; } if (boatStyle.courseVector !== false) { - let courseVectorDistance=(gps.speed !== undefined)?gps.speed*courseVectorTime:0; + let courseVectorDistance=(gps.speed === undefined || gps.course === undefined)?0:gps.speed*courseVectorTime; if (courseVectorDistance > 0) { - other = this.computeTarget(boatPosition, course, courseVectorDistance); + other = this.computeTarget(boatPosition, gps.course, courseVectorDistance); drawing.drawLineToContext([boatPosition, other], courseVectorStyle); } if (useHdg && boatRotation !== undefined && globalStore.getData(keys.properties.boatDirectionVector)) { @@ -206,17 +204,17 @@ NavLayer.prototype.onPostCompose=function(center,drawing){ if (! anchorDistance) { let radius1 = parseInt(globalStore.getData(keys.properties.navCircle1Radius)); if (radius1 > 10) { - other = this.computeTarget(boatPosition, course, radius1); + other = this.computeTarget(boatPosition, 0, radius1); drawing.drawCircleToContext(boatPosition, other, this.circleStyle); } let radius2 = parseInt(globalStore.getData(keys.properties.navCircle2Radius)); if (radius2 > 10 && radius2 > radius1) { - other = this.computeTarget(boatPosition, course, radius2); + other = this.computeTarget(boatPosition, 0, radius2); drawing.drawCircleToContext(boatPosition, other, this.circleStyle); } let radius3 = parseInt(globalStore.getData(keys.properties.navCircle3Radius)); if (radius3 > 10 && radius3 > radius2 && radius3 > radius1) { - other = this.computeTarget(boatPosition, course, radius3); + other = this.computeTarget(boatPosition, 0, radius3); drawing.drawCircleToContext(boatPosition, other, this.circleStyle); } } From 2b7f1f824c7fcd1b120313ef260152ef7e3f3aaf Mon Sep 17 00:00:00 2001 From: quantenschaum Date: Mon, 1 Jul 2024 15:35:11 +0200 Subject: [PATCH 2/8] adjusted depth calculation --- server/avnav_nmea.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/server/avnav_nmea.py b/server/avnav_nmea.py index 56b228874..a00079b40 100644 --- a/server/avnav_nmea.py +++ b/server/avnav_nmea.py @@ -459,7 +459,7 @@ def parseData(self,data,source='internal',sourcePriority=DEFAULT_SOURCE_PRIORITY Field Number: 1) Depth, meters 2) Offset from transducer, - positive means distance from tansducer to water line + positive means distance from transducer to water line negative means distance from transducer to keel 3) Checksum ''' @@ -471,7 +471,7 @@ def parseData(self,data,source='internal',sourcePriority=DEFAULT_SOURCE_PRIORITY self.addToNavData(rt,source=source,record=tag,priority=basePriority,timestamp=timestamp) return True return False - if tag == 'DBT': + if tag in ('DBT','DBK','DBS'): ''' DBT - Depth below transducer 1 2 3 4 5 6 7 @@ -486,8 +486,9 @@ def parseData(self,data,source='internal',sourcePriority=DEFAULT_SOURCE_PRIORITY 6) F = Fathoms 7) Checksum ''' - if darray[3]: - rt[self.K_DEPTHT.key] = float(darray[3]) + if darray[3] and darray[4]=='M': + K = self.K_DEPTHW if tag[2]=='S' else self.K_DEPTHK if tag[2]=='K' else self.K_DEPTHT + rt[K.key] = float(darray[3]) self.addToNavData(rt,source=source,record=tag,priority=basePriority,timestamp=timestamp) return True return False @@ -567,9 +568,9 @@ def parseData(self,data,source='internal',sourcePriority=DEFAULT_SOURCE_PRIORITY 3 set degrees magnetic 5 drift knots """ - if len(darray[1])>0 and darray[2]=="T": + if darray[1] and darray[2]=="T": rt[self.K_SET.key] = float(darray[1]) - if len(darray[5])>0 and darray[6]=="N": + if darray[5] and darray[6]=="N": rt[self.K_DFT.key] = float(darray[5]) self.addToNavData(rt,source=source,record=tag,priority=basePriority,timestamp=timestamp) return True @@ -604,7 +605,7 @@ def parseData(self,data,source='internal',sourcePriority=DEFAULT_SOURCE_PRIORITY if tag == 'MTW': # $--MTW,x.x,C*hh - if len(darray[1]) > 0: + if darray[1]: rt[self.K_VWTT.key] = float(darray[1])+273.15 self.addToNavData(rt,source=source,record=tag,priority=basePriority,timestamp=timestamp) return True From 735d47113872f75232f750a63a70c2ab0078d42f Mon Sep 17 00:00:00 2001 From: quantenschaum Date: Mon, 1 Jul 2024 20:37:14 +0200 Subject: [PATCH 3/8] zero sog detection also in heading mode if steady, course vectors are not shown --- viewer/map/navlayer.js | 2 +- viewer/nav/navdata.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/viewer/map/navlayer.js b/viewer/map/navlayer.js index b13df1d5c..667c075af 100644 --- a/viewer/map/navlayer.js +++ b/viewer/map/navlayer.js @@ -161,7 +161,7 @@ NavLayer.prototype.onPostCompose=function(center,drawing){ let gps=globalStore.getMultiple(positionKeys); let boatRotation=gps.boatDirection; let useHdg=gps.directionMode !== 'cog'; - let boatStyle=assign({},gps.isSteady?this.boatStyleSteady:(useHdg?this.boatStyleHdg:this.boatStyle)); + let boatStyle=assign({}, useHdg ? this.boatStyleHdg : (gps.isSteady ? this.boatStyleSteady : this.boatStyle)); if (boatStyle.rotate === false){ boatStyle.rotation=0; } diff --git a/viewer/nav/navdata.js b/viewer/nav/navdata.js index b724ef3dc..7e0175ad5 100644 --- a/viewer/nav/navdata.js +++ b/viewer/nav/navdata.js @@ -198,7 +198,7 @@ NavData.prototype.computeValues=function() { data.directionMode=boatDirectionMode; if (mapUseHdx) mapCourse=this.mapAverageHdm.val(); } - if (globalStore.getData(keys.properties.boatSteadyDetect) && data.directionMode === 'cog'){ + if (globalStore.getData(keys.properties.boatSteadyDetect)){ let maxSpeed=parseFloat(globalStore.getData(keys.properties.boatSteadyMax)) * navcompute.NM / 3600.0; if (this.speedAverage.val() === undefined || this.speedAverage.val() < maxSpeed){ data.isSteady=true; From 97cec7568e719f53adde854f67890ec575b0a85b Mon Sep 17 00:00:00 2001 From: quantenschaum Date: Tue, 2 Jul 2024 15:59:46 +0200 Subject: [PATCH 4/8] set widget default values to '---' to avoid displaying 0 if the value is actually undefined --- viewer/components/SKWidgets.jsx | 24 +++++++++++------------- viewer/components/WidgetList.js | 16 ++++++++++------ 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/viewer/components/SKWidgets.jsx b/viewer/components/SKWidgets.jsx index 67d569f11..a48609180 100644 --- a/viewer/components/SKWidgets.jsx +++ b/viewer/components/SKWidgets.jsx @@ -26,13 +26,12 @@ export class SKRollWidget extends React.Component{ render(){ let value=DegreeFormatter(this.props.value,this.props.inDegree); - let degreeArrow = "0"; - // arrow left + Wert - if (this.props.value <0 && this.props.value !== 0){ + let degreeArrow = "-"; + if (this.props.value == 0) { + degreeArrow = "0"; + } else if (this.props.value < 0) { degreeArrow = "\u21D0" + value; - } - // value + space + arrow right - if (this.props.value >0 && this.props.value !== 0){ + } else if (this.props.value > 0) { degreeArrow = value + "\xA0\u21D2"; } let classes="widget SKRollWidget "+this.props.className||""; @@ -80,13 +79,12 @@ export class SKPitchWidget extends React.Component{ render(){ let value=DegreeFormatter(this.props.value,this.props.inDegree); - let degreeArrow = "0"; - // arrow left + Wert - if (this.props.value <0 && this.props.value !== 0){ + let degreeArrow = "-"; + if (this.props.value == 0) { + degreeArrow = "0"; + } else if (this.props.value < 0) { degreeArrow = value + "\xA0\u21D3"; - } - // value + space + arrow right - if (this.props.value >0 && this.props.value !== 0){ + } else if (this.props.value > 0) { degreeArrow = value + "\xA0\u21D1"; } let classes="widget SKPitchWidget "+this.props.className||""; @@ -123,4 +121,4 @@ SKPitchWidget.editableParameters={ inDegree:{type:'BOOLEAN',default:false,description:'set to true if input is in deg instead of rad'}, criticalValue: {type: 'NUMBER', default: 45}, caption: {type:'STRING',default:'Pitch'} -} \ No newline at end of file +} diff --git a/viewer/components/WidgetList.js b/viewer/components/WidgetList.js index a22927f4d..5e8f29c5e 100644 --- a/viewer/components/WidgetList.js +++ b/viewer/components/WidgetList.js @@ -25,7 +25,7 @@ import assign from 'object-assign'; let widgetList=[ { name: 'SOG', - default: "0.0", + default: "---", unit: "kn", caption: 'SOG', storeKeys: { @@ -100,7 +100,7 @@ let widgetList=[ }, { name: 'DST', - default: "----", + default: "---", unit: "nm", caption: 'DST', storeKeys:{ @@ -124,7 +124,7 @@ let widgetList=[ }, { name: 'VMG', - default: "0.0", + default: "---", unit: "kn", caption: 'VMG', storeKeys: { @@ -135,7 +135,7 @@ let widgetList=[ }, { name: 'STW', - default: '0.0', + default: '---', unit: 'kn', caption: 'STW', storeKeys:{ @@ -251,7 +251,7 @@ let widgetList=[ { name: 'RteDistance', - default: " ----- ", + default: "---", unit: "nm", caption: 'RTE-Dst', storeKeys:{ @@ -323,6 +323,7 @@ let widgetList=[ }, { name: 'DepthDisplay', + default: "---", caption: 'DPT', unit: 'm', storeKeys:{ @@ -373,7 +374,8 @@ let widgetList=[ storeKeys: RoutePointsWidget.storeKeys }, { - name: 'Default' //a way to access the default widget providing all parameters in the layout + name: 'Default', //a way to access the default widget providing all parameters in the layout + default: "---", }, { name: 'RadialGauge', @@ -389,11 +391,13 @@ let widgetList=[ }, { name: 'signalKPressureHpa', + default: "---", unit: 'hPa', formatter: 'skPressure' }, { name:'signalKCelsius', + default: "---", unit:'°', formatter: 'skTemperature' }, From 2bcfc1e993ee356f08645744ada4a47ed1b03d8d Mon Sep 17 00:00:00 2001 From: quantenschaum Date: Tue, 2 Jul 2024 16:33:29 +0200 Subject: [PATCH 5/8] fallback to circle boat icon if boatRotation is undefined --- viewer/map/navlayer.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/viewer/map/navlayer.js b/viewer/map/navlayer.js index 667c075af..1deb569f3 100644 --- a/viewer/map/navlayer.js +++ b/viewer/map/navlayer.js @@ -162,14 +162,15 @@ NavLayer.prototype.onPostCompose=function(center,drawing){ let boatRotation=gps.boatDirection; let useHdg=gps.directionMode !== 'cog'; let boatStyle=assign({}, useHdg ? this.boatStyleHdg : (gps.isSteady ? this.boatStyleSteady : this.boatStyle)); - if (boatStyle.rotate === false){ - boatStyle.rotation=0; + if (boatRotation === undefined) { + boatStyle=this.boatStyleSteady; } - else { - if (boatRotation !== undefined){ + if (boatStyle.rotate === false) { + boatStyle.rotation = 0; + } else { + if (boatRotation !== undefined) { boatStyle.rotation = boatRotation * Math.PI / 180; - } - else{ + } else { boatStyle.rotation = 0; } } From cfb8a86bfce3afef30c86cf80281fe99a707bc11 Mon Sep 17 00:00:00 2001 From: quantenschaum Date: Sun, 7 Jul 2024 21:25:40 +0200 Subject: [PATCH 6/8] merge dicts to collect nested additionalKeys fixes #347 --- viewer/nav/gpsdata.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/viewer/nav/gpsdata.js b/viewer/nav/gpsdata.js index 6961b23b7..13a85f5da 100644 --- a/viewer/nav/gpsdata.js +++ b/viewer/nav/gpsdata.js @@ -30,7 +30,7 @@ const specialHandling=[ */ const GpsData=function(){ - + this.timer=null; this.gpsErrors=0; @@ -156,7 +156,12 @@ GpsData.prototype.handleGpsResponse=function(data, status){ for (let k in data){ if (ignoredKeys.indexOf(k)>=0) continue; if (this.filteredStoreKeys[k]) continue; //ignore any key we use internally - this.additionalKeys[k]=this.computeKeys(k,data[k],base); + let keys=this.computeKeys(k,data[k],base); + if(typeof(keys)=="object" && typeof(this.additionalKeys[k])=="object") { + keys = assign(this.additionalKeys[k],keys); + } else { + this.additionalKeys[k]=keys; + } gpsdata[k]=data[k]; } this.writeToStore(gpsdata,this.additionalKeys); From 3c9cbf875251b27c215d9d97fecb7ce76aae5d3c Mon Sep 17 00:00:00 2001 From: quantenschaum Date: Tue, 9 Jul 2024 13:59:41 +0200 Subject: [PATCH 7/8] limit heading to 0-360 --- server/avnav_nmea.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/server/avnav_nmea.py b/server/avnav_nmea.py index a00079b40..607d77fa2 100644 --- a/server/avnav_nmea.py +++ b/server/avnav_nmea.py @@ -528,7 +528,7 @@ def parseData(self,data,source='internal',sourcePriority=DEFAULT_SOURCE_PRIORITY elif MagDevDir == 'W': heading_m -= MagDeviation rt[self.K_MDEV.key] = -MagDeviation - rt[self.K_HDGM.key] = heading_m + rt[self.K_HDGM.key] = to360(heading_m) # True course heading_t = None @@ -540,7 +540,7 @@ def parseData(self,data,source='internal',sourcePriority=DEFAULT_SOURCE_PRIORITY heading_t = heading_m - MagVariation rt[self.K_MVAR.key] = -MagVariation if heading_t is not None: - rt[self.K_HDGT.key] = heading_t + rt[self.K_HDGT.key] = to360(heading_t) self.addToNavData(rt,source=source,record=tag,priority=basePriority,timestamp=timestamp) return True @@ -770,3 +770,7 @@ def storeAISdata(self,bitfield,source='internal',priority=0,timestamp=None): return self.navdata.setAisValue(mmsi,AVNUtil.convertAIS(rt),source=source,priority=priority,timestamp=timestamp) +def to360(a): + "limit a to [0,360)" + while a < 0: a += 360 + return a % 360 From c064667fce0beaba86fe09a36c5055694296b009 Mon Sep 17 00:00:00 2001 From: quantenschaum Date: Thu, 22 Aug 2024 13:13:28 +0200 Subject: [PATCH 8/8] cleanup of names --- server/avnav_nmea.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/avnav_nmea.py b/server/avnav_nmea.py index 607d77fa2..a80d2bd54 100644 --- a/server/avnav_nmea.py +++ b/server/avnav_nmea.py @@ -66,11 +66,11 @@ class NMEAParser(object): K_HDGT=Key('headingTrue','true heading','\N{DEGREE SIGN}','navigation.headingTrue',signalKConversion=AVNUtil.rad2deg) K_VWTT=Key('waterTemp','water temperature','k',signalK='environment.water.temperature') K_VHWS=Key('waterSpeed','speed through water','m/s','navigation.speedThroughWater') - K_TWS=Key('trueWindSpeed','wind speed true (speed through water ref or ground ref)','m/s','environment.wind.speedTrue') - K_AWS=Key('windSpeed','apparent wind speed in m/s','m/s','environment.wind.speedApparent') + K_AWS=Key('windSpeed','apparent wind speed','m/s','environment.wind.speedApparent') + K_AWA=Key('windAngle','apparent wind angle','\N{DEGREE SIGN}','environment.wind.angleApparent',signalKConversion=AVNUtil.rad2deg) + K_TWS=Key('trueWindSpeed','true wind speed','m/s','environment.wind.speedTrue') K_TWA=Key('trueWindAngle','true wind angle','\N{DEGREE SIGN}','environment.wind.angleTrueWater',signalKConversion=AVNUtil.rad2deg) K_TWD=Key('trueWindDirection','true wind direction','\N{DEGREE SIGN}') - K_AWA=Key('windAngle','apparent wind angle','\N{DEGREE SIGN}','environment.wind.angleApparent',signalKConversion=AVNUtil.rad2deg) K_MDEV=Key('magDeviation', 'magnetic Deviation in deg','\N{DEGREE SIGN}', signalK='navigation.magneticDeviation', signalKConversion=AVNUtil.rad2deg) K_MVAR=Key('magVariation', 'magnetic Variation in deg','\N{DEGREE SIGN}', signalK='navigation.magneticVariation', signalKConversion=AVNUtil.rad2deg) K_LAT=Key('lat','gps latitude',signalK='navigation.position.latitude')