[401] | 1 |
|
---|
| 2 | from util import utf2unicode
|
---|
[829] | 3 | from flight import Flight
|
---|
[298] | 4 |
|
---|
| 5 | import const
|
---|
| 6 | import cPickle as pickle
|
---|
[829] | 7 | import calendar
|
---|
[743] | 8 | import datetime
|
---|
| 9 | import time
|
---|
[97] | 10 |
|
---|
| 11 | #------------------------------------------------------------------------------
|
---|
| 12 |
|
---|
[298] | 13 | ## @package mlx.pirep
|
---|
| 14 | #
|
---|
| 15 | # The PIREP module.
|
---|
| 16 | #
|
---|
| 17 | # This module defines only one class, \ref PIREP. It is used to extract and
|
---|
| 18 | # store the information needed for a PIREP. The saved PIREPs are pickled
|
---|
| 19 | # instances of this class.
|
---|
[97] | 20 |
|
---|
| 21 | #------------------------------------------------------------------------------
|
---|
| 22 |
|
---|
| 23 | class PIREP(object):
|
---|
| 24 | """A pilot's report of a flight."""
|
---|
[855] | 25 | class Message(object):
|
---|
| 26 | """A message belonging to the PIREP."""
|
---|
| 27 | @staticmethod
|
---|
| 28 | def fromMessageData(messageData):
|
---|
| 29 | """Construct a message from a JSON message data."""
|
---|
| 30 | message = messageData["message"]
|
---|
| 31 | senderPID = messageData["senderPID"]
|
---|
| 32 | senderName = messageData["senderName"]
|
---|
| 33 |
|
---|
| 34 | return PIREP.Message(message, senderPID, senderName)
|
---|
| 35 |
|
---|
| 36 | def __init__(self, message, senderPID, senderName):
|
---|
| 37 | """Construct the message object."""
|
---|
| 38 | self.message = message
|
---|
| 39 | self.senderPID = senderPID
|
---|
| 40 | self.senderName = senderName
|
---|
| 41 |
|
---|
[743] | 42 | _flightTypes = { const.FLIGHTTYPE_SCHEDULED : "SCHEDULED",
|
---|
| 43 | const.FLIGHTTYPE_OLDTIMER : "OT",
|
---|
| 44 | const.FLIGHTTYPE_VIP : "VIP",
|
---|
| 45 | const.FLIGHTTYPE_CHARTER : "CHARTER" }
|
---|
| 46 |
|
---|
[97] | 47 | @staticmethod
|
---|
| 48 | def _formatLine(timeStr, line):
|
---|
| 49 | """Format the given time string and line as needed for the ACARS and
|
---|
| 50 | some other things."""
|
---|
| 51 | return "[" + timeStr + "]-[" + line + "]"
|
---|
[151] | 52 |
|
---|
| 53 | @staticmethod
|
---|
[743] | 54 | def formatTimestampForRPC(t):
|
---|
| 55 | """Format the given timestamp for RPC."""
|
---|
| 56 | return time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(t))
|
---|
| 57 |
|
---|
| 58 | @staticmethod
|
---|
[829] | 59 | def parseTimestampFromRPC(s):
|
---|
| 60 | """Format the given timestamp for RPC."""
|
---|
| 61 | dt = datetime.datetime.strptime(s, "%Y-%m-%d %H:%M:%S")
|
---|
| 62 | return calendar.timegm(dt.utctimetuple())
|
---|
| 63 |
|
---|
| 64 | @staticmethod
|
---|
| 65 | def decodeFlightTypeText(s):
|
---|
| 66 | """Decode the given flight type text."""
|
---|
| 67 | for (flighType, text) in PIREP._flightTypes.iteritems():
|
---|
| 68 | if s==text:
|
---|
| 69 | return flighType
|
---|
[856] | 70 | return const.FLIGHTTYPE_SCHEDULED
|
---|
[829] | 71 |
|
---|
| 72 | @staticmethod
|
---|
| 73 | def parseLogFromRPC(log):
|
---|
| 74 | """Parse the given log coming from the RPC."""
|
---|
| 75 | index = 0
|
---|
| 76 | entries = []
|
---|
| 77 |
|
---|
| 78 | inTimeStr = False
|
---|
| 79 | inEntry = False
|
---|
| 80 |
|
---|
| 81 | timestr = ""
|
---|
| 82 | entry = ""
|
---|
| 83 |
|
---|
| 84 | while index<len(log):
|
---|
| 85 | c = log[index]
|
---|
| 86 | index += 1
|
---|
| 87 |
|
---|
| 88 | if c==']':
|
---|
| 89 | if inEntry:
|
---|
| 90 | entries.append((timestr, entry))
|
---|
| 91 | timestr = ""
|
---|
| 92 | entry = ""
|
---|
| 93 |
|
---|
| 94 | inTimeStr = False
|
---|
| 95 | inEntry = False
|
---|
| 96 | elif not inTimeStr and not inEntry:
|
---|
| 97 | if c=='[':
|
---|
| 98 | if timestr:
|
---|
| 99 | inEntry = True
|
---|
| 100 | else:
|
---|
| 101 | inTimeStr = True
|
---|
| 102 | elif inTimeStr:
|
---|
| 103 | timestr += c
|
---|
| 104 | elif inEntry:
|
---|
| 105 | entry += c
|
---|
| 106 |
|
---|
| 107 | return entries
|
---|
| 108 |
|
---|
| 109 | @staticmethod
|
---|
[151] | 110 | def load(path):
|
---|
| 111 | """Load a PIREP from the given path.
|
---|
| 112 |
|
---|
| 113 | Returns the PIREP object, or None on error."""
|
---|
| 114 | try:
|
---|
| 115 | with open(path, "rb") as f:
|
---|
[303] | 116 | pirep = pickle.load(f)
|
---|
| 117 | if "numCrew" not in dir(pirep):
|
---|
| 118 | pirep.numCrew = pirep.bookedFlight.numCrew
|
---|
| 119 | if "numPassengers" not in dir(pirep):
|
---|
| 120 | pirep.numPassengers = pirep.bookedFlight.numPassengers
|
---|
| 121 | if "bagWeight" not in dir(pirep):
|
---|
| 122 | pirep.bagWeight = pirep.bookedFlight.bagWeight
|
---|
| 123 | if "mailWeight" not in dir(pirep):
|
---|
| 124 | pirep.mailWeight = pirep.bookedFlight.mailWeight
|
---|
| 125 | return pirep
|
---|
[151] | 126 | except Exception, e:
|
---|
[401] | 127 | print "Failed loading PIREP from %s: %s" % (path,
|
---|
| 128 | utf2unicode(str(e)))
|
---|
[151] | 129 | return None
|
---|
[345] | 130 |
|
---|
[262] | 131 | def __init__(self, flight):
|
---|
| 132 | """Initialize the PIREP from the given flight."""
|
---|
[829] | 133 | if flight is None:
|
---|
| 134 | return
|
---|
| 135 |
|
---|
[262] | 136 | self.bookedFlight = flight.bookedFlight
|
---|
[303] | 137 |
|
---|
[908] | 138 | self.numCockpitCrew = flight.numCockpitCrew
|
---|
| 139 | self.numCabinCrew = flight.numCabinCrew
|
---|
[303] | 140 | self.numCrew = flight.numCrew
|
---|
| 141 | self.numPassengers = flight.numPassengers
|
---|
| 142 | self.bagWeight = flight.bagWeight
|
---|
[262] | 143 | self.cargoWeight = flight.cargoWeight
|
---|
[303] | 144 | self.mailWeight = flight.mailWeight
|
---|
[345] | 145 |
|
---|
[262] | 146 | self.filedCruiseAltitude = flight.filedCruiseAltitude
|
---|
| 147 | self.cruiseAltitude = flight.cruiseAltitude
|
---|
| 148 | self.route = flight.route
|
---|
[97] | 149 |
|
---|
[262] | 150 | self.departureMETAR = flight.departureMETAR.upper()
|
---|
| 151 | self.arrivalMETAR = flight.arrivalMETAR.upper()
|
---|
[97] | 152 |
|
---|
[262] | 153 | self.departureRunway = flight.departureRunway.upper()
|
---|
| 154 | self.sid = flight.sid.upper()
|
---|
[97] | 155 |
|
---|
[262] | 156 | self.star = flight.star
|
---|
| 157 | self.transition = flight.transition
|
---|
| 158 | self.approachType = flight.approachType.upper()
|
---|
| 159 | self.arrivalRunway = flight.arrivalRunway.upper()
|
---|
[97] | 160 |
|
---|
[262] | 161 | self.flightType = flight.flightType
|
---|
| 162 | self.online = flight.online
|
---|
[97] | 163 |
|
---|
[262] | 164 | self.comments = flight.comments
|
---|
| 165 | self.flightDefects = flight.flightDefects
|
---|
| 166 | self.delayCodes = flight.delayCodes
|
---|
[345] | 167 |
|
---|
[97] | 168 | self.blockTimeStart = flight.blockTimeStart
|
---|
| 169 | self.flightTimeStart = flight.flightTimeStart
|
---|
| 170 | self.flightTimeEnd = flight.flightTimeEnd
|
---|
| 171 | self.blockTimeEnd = flight.blockTimeEnd
|
---|
| 172 | self.flownDistance = flight.flownDistance
|
---|
[102] | 173 | self.fuelUsed = flight.startFuel - flight.endFuel
|
---|
[97] | 174 |
|
---|
[262] | 175 | logger = flight.logger
|
---|
[97] | 176 | self.rating = logger.getRating()
|
---|
| 177 | self.logLines = logger.lines
|
---|
| 178 | self.faultLineIndexes = logger.faultLineIndexes
|
---|
[345] | 179 |
|
---|
[855] | 180 | self.messages = []
|
---|
| 181 |
|
---|
[829] | 182 | def setupFromPIREPData(self, pirepData, bookedFlight):
|
---|
| 183 |
|
---|
| 184 | self.bookedFlight = bookedFlight
|
---|
| 185 |
|
---|
[908] | 186 | self.numCockpitCrew = int(pirepData["numCockpitCrew"])
|
---|
| 187 | self.numCabinCrew = int(pirepData["numCabinCrew"])
|
---|
| 188 | self.numCrew = self.numCockpitCrew + self.numCabinCrew
|
---|
[829] | 189 | self.numPassengers = int(pirepData["numPassengers"])
|
---|
| 190 | self.bagWeight = int(pirepData["bagWeight"])
|
---|
| 191 | self.cargoWeight = int(pirepData["cargoWeight"])
|
---|
| 192 | self.mailWeight = int(pirepData["mailWeight"])
|
---|
| 193 |
|
---|
[856] | 194 | filedCruiseLevel = pirepData["filedCruiseLevel"].strip()
|
---|
| 195 | if filedCruiseLevel:
|
---|
| 196 | if filedCruiseLevel.startswith("FL"):
|
---|
| 197 | filedCruiseLevel = filedCruiseLevel[2:]
|
---|
| 198 | if filedCruiseLevel:
|
---|
| 199 | self.filedCruiseAltitude = int(filedCruiseLevel)*100
|
---|
| 200 | else:
|
---|
| 201 | self.filedCruiseAltitude = 10000;
|
---|
| 202 |
|
---|
[854] | 203 | cruiseLevel = pirepData["cruiseLevel"].strip()
|
---|
| 204 | if cruiseLevel:
|
---|
| 205 | if cruiseLevel.startswith("FL"):
|
---|
| 206 | cruiseLevel = cruiseLevel[2:]
|
---|
[829] | 207 | if cruiseLevel:
|
---|
| 208 | self.cruiseAltitude = int(cruiseLevel[2:])*100
|
---|
| 209 | else:
|
---|
| 210 | self.cruiseAltitude = self.filedCruiseAltitude
|
---|
| 211 | self.route = pirepData["route"]
|
---|
| 212 |
|
---|
| 213 | self.departureMETAR = pirepData["departureMETAR"]
|
---|
| 214 | self.arrivalMETAR = pirepData["arrivalMETAR"]
|
---|
| 215 |
|
---|
| 216 | self.departureRunway = pirepData["departureRunway"]
|
---|
| 217 | self.sid = pirepData["sid"]
|
---|
| 218 |
|
---|
| 219 | star = pirepData["star"].split(",")
|
---|
| 220 | self.star = star[0]
|
---|
| 221 | self.star.strip()
|
---|
| 222 |
|
---|
| 223 | if len(star)>1:
|
---|
| 224 | self.transition = star[1]
|
---|
| 225 | self.transition.strip()
|
---|
| 226 | else:
|
---|
| 227 | self.transition = ""
|
---|
| 228 | self.approachType = pirepData["approachType"]
|
---|
| 229 | self.arrivalRunway = pirepData["arrivalRunway"]
|
---|
| 230 |
|
---|
| 231 | self.flightType = PIREP.decodeFlightTypeText(pirepData["flightType"])
|
---|
| 232 | self.online = int(pirepData["online"])!=0
|
---|
| 233 |
|
---|
| 234 | self.comments = pirepData["comments"]
|
---|
| 235 | self.flightDefects = pirepData["flightDefects"]
|
---|
| 236 | self.delayCodes = pirepData["timeComment"]
|
---|
| 237 | if self.delayCodes=="UTC":
|
---|
[838] | 238 | self.delayCodes = []
|
---|
| 239 | else:
|
---|
| 240 | self.delayCodes = self.delayCodes.split(", ")
|
---|
[829] | 241 |
|
---|
| 242 | flightDate = pirepData["flightDate"] + " "
|
---|
| 243 |
|
---|
| 244 | self.blockTimeStart = \
|
---|
| 245 | PIREP.parseTimestampFromRPC(flightDate + pirepData["blockTimeStart"])
|
---|
| 246 | self.flightTimeStart = \
|
---|
| 247 | PIREP.parseTimestampFromRPC(flightDate + pirepData["flightTimeStart"])
|
---|
| 248 | self.flightTimeEnd = \
|
---|
| 249 | PIREP.parseTimestampFromRPC(flightDate + pirepData["flightTimeEnd"])
|
---|
| 250 | self.blockTimeEnd = \
|
---|
| 251 | PIREP.parseTimestampFromRPC(flightDate + pirepData["blockTimeEnd"])
|
---|
| 252 | self.flownDistance = float(pirepData["flownDistance"])
|
---|
| 253 | self.fuelUsed = float(pirepData["fuelUsed"])
|
---|
| 254 |
|
---|
| 255 | # logger = flight.logger
|
---|
| 256 | self.rating = float(pirepData["rating"])
|
---|
| 257 |
|
---|
| 258 | log = pirepData["log"]
|
---|
| 259 |
|
---|
| 260 | self.logLines = PIREP.parseLogFromRPC(log)[1:]
|
---|
| 261 | if self.logLines and \
|
---|
| 262 | (self.logLines[0][0]=="LOGGER NG LOG" or
|
---|
| 263 | self.logLines[0][0]=="MAVA LOGGER X"):
|
---|
| 264 | self.logLines = self.logLines[1:]
|
---|
| 265 | numLogLines = len(self.logLines)
|
---|
| 266 |
|
---|
| 267 | lastFaultLineIndex = 0
|
---|
| 268 | self.faultLineIndexes = []
|
---|
| 269 | for ratingText in pirepData["ratingText"].splitlines()[:-1]:
|
---|
| 270 | faultLines = PIREP.parseLogFromRPC(ratingText)
|
---|
| 271 | for (timeStr, entry) in faultLines:
|
---|
| 272 | for i in range(lastFaultLineIndex, numLogLines-1):
|
---|
| 273 | if timeStr>=self.logLines[i][0] and \
|
---|
| 274 | timeStr<self.logLines[i+1][0]:
|
---|
| 275 | self.logLines = self.logLines[:i+1] + \
|
---|
| 276 | [(timeStr, entry)] + self.logLines[i+1:]
|
---|
| 277 | self.faultLineIndexes.append(i+1)
|
---|
| 278 | lastFaultLineIndex = i+1
|
---|
| 279 | numLogLines += 1
|
---|
| 280 | break
|
---|
| 281 |
|
---|
[855] | 282 | self.messages = []
|
---|
| 283 | for messageData in pirepData["messages"]:
|
---|
| 284 | self.messages.append(PIREP.Message.fromMessageData(messageData))
|
---|
| 285 |
|
---|
[743] | 286 | @property
|
---|
[778] | 287 | def flightDateText(self):
|
---|
| 288 | """Get the text version of the booked flight's departure time."""
|
---|
| 289 | return self.bookedFlight.departureTime.strftime("%Y-%m-%d")
|
---|
| 290 |
|
---|
| 291 | @property
|
---|
[743] | 292 | def flightTypeText(self):
|
---|
| 293 | """Get the text representation of the flight type."""
|
---|
| 294 | return PIREP._flightTypes[self.flightType]
|
---|
| 295 |
|
---|
| 296 | @property
|
---|
| 297 | def blockTimeStartText(self):
|
---|
| 298 | """Get the beginning of the block time in string format."""
|
---|
| 299 | return PIREP.formatTimestampForRPC(self.blockTimeStart)
|
---|
| 300 |
|
---|
| 301 | @property
|
---|
| 302 | def flightTimeStartText(self):
|
---|
| 303 | """Get the beginning of the flight time in string format."""
|
---|
| 304 | return PIREP.formatTimestampForRPC(self.flightTimeStart)
|
---|
| 305 |
|
---|
| 306 | @property
|
---|
| 307 | def flightTimeEndText(self):
|
---|
| 308 | """Get the end of the flight time in string format."""
|
---|
| 309 | return PIREP.formatTimestampForRPC(self.flightTimeEnd)
|
---|
| 310 |
|
---|
| 311 | @property
|
---|
| 312 | def blockTimeEndText(self):
|
---|
| 313 | """Get the end of the block time in string format."""
|
---|
| 314 | return PIREP.formatTimestampForRPC(self.blockTimeEnd)
|
---|
| 315 |
|
---|
[97] | 316 | def getACARSText(self):
|
---|
| 317 | """Get the ACARS text.
|
---|
| 318 |
|
---|
| 319 | This is a specially formatted version of the log without the faults."""
|
---|
| 320 | text = "[MAVA LOGGER X LOG]-[%s]" % (const.VERSION,)
|
---|
| 321 | for index in range(0, len(self.logLines)):
|
---|
| 322 | if index not in self.faultLineIndexes:
|
---|
| 323 | (timeStr, line) = self.logLines[index]
|
---|
| 324 | if timeStr is not None:
|
---|
| 325 | text += PIREP._formatLine(timeStr, line)
|
---|
| 326 | return text
|
---|
| 327 |
|
---|
| 328 | def getRatingText(self):
|
---|
| 329 | """Get the rating text.
|
---|
| 330 |
|
---|
| 331 | This is a specially formatted version of the lines containing the
|
---|
| 332 | faults."""
|
---|
| 333 | text = ""
|
---|
| 334 | for index in self.faultLineIndexes:
|
---|
| 335 | (timeStr, line) = self.logLines[index]
|
---|
| 336 | if timeStr is not None:
|
---|
| 337 | text += PIREP._formatLine(timeStr, line)
|
---|
[345] | 338 | text += "\n"
|
---|
[97] | 339 |
|
---|
| 340 | text += "\n[Flight Rating: %.1f]" % (max(0.0, self.rating),)
|
---|
| 341 |
|
---|
| 342 | return text
|
---|
[345] | 343 |
|
---|
[97] | 344 | def getTimeComment(self):
|
---|
| 345 | """Get the time comment.
|
---|
| 346 |
|
---|
| 347 | This is basically a collection of the delay codes, if any."""
|
---|
[99] | 348 | if not self.delayCodes:
|
---|
| 349 | return "UTC"
|
---|
| 350 | else:
|
---|
| 351 | s = ""
|
---|
| 352 | for code in self.delayCodes:
|
---|
| 353 | if s: s += ", "
|
---|
[437] | 354 | s += code
|
---|
[99] | 355 | return s
|
---|
[97] | 356 |
|
---|
| 357 | def getSTAR(self):
|
---|
| 358 | """Get the STAR and/or the transition."""
|
---|
| 359 | star = self.star if self.star is not None else ""
|
---|
| 360 | if self.transition is not None:
|
---|
| 361 | if star: star += ", "
|
---|
| 362 | star += self.transition
|
---|
| 363 | return star.upper()
|
---|
[151] | 364 |
|
---|
| 365 | def save(self, path):
|
---|
| 366 | """Save the PIREP to the given file.
|
---|
| 367 |
|
---|
| 368 | Returns whether the saving has succeeded."""
|
---|
| 369 | try:
|
---|
| 370 | with open(path, "wb") as f:
|
---|
| 371 | pickle.dump(self, f)
|
---|
[393] | 372 | return None
|
---|
[151] | 373 | except Exception, e:
|
---|
[401] | 374 | error = utf2unicode(str(e))
|
---|
| 375 | print u"Failed saving PIREP to %s: %s" % (path, error)
|
---|
[393] | 376 | return error
|
---|
[743] | 377 |
|
---|
| 378 | def _serialize(self):
|
---|
| 379 | """Serialize the PIREP for JSON-RPC."""
|
---|
| 380 | attrs = {}
|
---|
| 381 | attrs["log"] = self.getACARSText()
|
---|
[778] | 382 | attrs["flightDate"] = self.flightDateText
|
---|
| 383 | attrs["callsign"] = self.bookedFlight.callsign
|
---|
| 384 | attrs["departureICAO"] = self.bookedFlight.departureICAO
|
---|
| 385 | attrs["arrivalICAO"] = self.bookedFlight.arrivalICAO
|
---|
[743] | 386 | attrs["numPassengers"] = self.numPassengers
|
---|
[908] | 387 | attrs["numCockpitCrew"] = self.numCockpitCrew
|
---|
| 388 | attrs["numCabinCrew"] = self.numCabinCrew
|
---|
[743] | 389 | attrs["numCrew"] = self.numCrew
|
---|
| 390 | attrs["cargoWeight"] = self.cargoWeight
|
---|
| 391 | attrs["bagWeight"] = self.bagWeight
|
---|
| 392 | attrs["mailWeight"] = self.mailWeight
|
---|
| 393 | attrs["flightType"] = self.flightTypeText
|
---|
| 394 | attrs["online"] = 1 if self.online else 0
|
---|
| 395 | attrs["blockTimeStart"] = self.blockTimeStartText
|
---|
| 396 | attrs["blockTimeEnd"] = self.blockTimeEndText
|
---|
| 397 | attrs["flightTimeStart"] = self.flightTimeStartText
|
---|
| 398 | attrs["flightTimeEnd"] = self.flightTimeEndText
|
---|
| 399 | attrs["timeComment"] = self.getTimeComment()
|
---|
| 400 | attrs["fuelUsed"] = self.fuelUsed
|
---|
| 401 | attrs["departureRunway"] = self.departureRunway
|
---|
| 402 | attrs["arrivalRunway"] = self.arrivalRunway
|
---|
| 403 | attrs["departureMETAR"] = self.departureMETAR
|
---|
| 404 | attrs["arrivalMETAR"] = self.arrivalMETAR
|
---|
| 405 | attrs["filedCruiseLevel"] = self.filedCruiseAltitude / 100.0
|
---|
| 406 | attrs["cruiseLevel"] = self.cruiseAltitude / 100.0
|
---|
| 407 | attrs["sid"] = self.sid
|
---|
| 408 | attrs["route"] = self.route
|
---|
| 409 | attrs["star"] = self.star
|
---|
| 410 | attrs["approachType"] = self.approachType
|
---|
| 411 | attrs["comments"] = self.comments
|
---|
| 412 | attrs["flightDefects"] = self.flightDefects
|
---|
| 413 | attrs["ratingText"] = self.getRatingText()
|
---|
| 414 | attrs["rating"] = max(0.0, self.rating)
|
---|
[839] | 415 | attrs["flownDistance"] = "%.2f" % (self.flownDistance,)
|
---|
[743] | 416 | # FIXME: it should be stored in the PIREP when it is sent later
|
---|
[778] | 417 | attrs["performDate"] = datetime.date.today().strftime("%Y-%m-%d")
|
---|
[743] | 418 |
|
---|
| 419 | return ([], attrs)
|
---|