source: src/mlx/pirep.py@ 1033:330058d37574

python3
Last change on this file since 1033:330058d37574 was 1033:330058d37574, checked in by István Váradi <ivaradi@…>, 2 years ago

Updates for the new crew and passenger handling (re #357)

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