source: src/mlx/web.py@ 98:f017d907fa39

Last change on this file since 98:f017d907fa39 was 97:f885322fb296, checked in by István Váradi <ivaradi@…>, 13 years ago

The PIREP can be created and sent.

File size: 22.8 KB
Line 
1# Interface towards the websites used
2
3#------------------------------------------------------------------------------
4
5import const
6import util
7
8import threading
9import sys
10import urllib
11import urllib2
12import hashlib
13import time
14import datetime
15import codecs
16import xml.sax
17
18#---------------------------------------------------------------------------------------
19
20def readline(f):
21 """Read a line from the given file.
22
23 The line is stripped and empty lines are discarded."""
24 while True:
25 line = f.readline()
26 if not line: return ""
27 line = line.strip()
28 if line:
29 return line
30
31#---------------------------------------------------------------------------------------
32
33class BookedFlight(object):
34 """A flight that was booked."""
35 TYPECODE2TYPE = { "736" : const.AIRCRAFT_B736,
36 "73G" : const.AIRCRAFT_B737,
37 "738" : const.AIRCRAFT_B738,
38 "733" : const.AIRCRAFT_B733,
39 "734" : const.AIRCRAFT_B734,
40 "735" : const.AIRCRAFT_B735,
41 "DH4" : const.AIRCRAFT_DH8D,
42 "762" : const.AIRCRAFT_B762,
43 "763" : const.AIRCRAFT_B763,
44 "CR2" : const.AIRCRAFT_CRJ2,
45 "F70" : const.AIRCRAFT_F70,
46 "LI2" : const.AIRCRAFT_DC3,
47 "TU3" : const.AIRCRAFT_T134,
48 "TU5" : const.AIRCRAFT_T154,
49 "YK4" : const.AIRCRAFT_YK40 }
50
51 def __init__(self, id, f):
52 """Construct a booked flight with the given ID.
53
54 The rest of the data is read from the given file."""
55
56 self.id = id
57 self.callsign = readline(f)
58
59 date = readline(f)
60
61 self.departureICAO = readline(f)
62 self.arrivalICAO = readline(f)
63
64 self._readAircraftType(f)
65 self.tailNumber = readline(f)
66 self.numPassengers = int(readline(f))
67 self.numCrew = int(readline(f))
68 self.bagWeight = int(readline(f))
69 self.cargoWeight = int(readline(f))
70 self.mailWeight = int(readline(f))
71 self.route = readline(f)
72
73 departureTime = readline(f)
74 self.departureTime = datetime.datetime.strptime(date + " " + departureTime,
75 "%Y-%m-%d %H:%M:%S")
76
77 arrivalTime = readline(f)
78 self.arrivalTime = datetime.datetime.strptime(date + " " + arrivalTime,
79 "%Y-%m-%d %H:%M:%S")
80
81 if not readline(f)==".NEXT.":
82 raise Exception("Invalid line in flight data")
83
84 def _readAircraftType(self, f):
85 """Read the aircraft type from the given file."""
86 line = readline(f)
87 typeCode = line[:3]
88 if typeCode in self.TYPECODE2TYPE:
89 self.aircraftType = self.TYPECODE2TYPE[typeCode]
90 else:
91 raise Exception("Invalid aircraft type code: '" + typeCode + "'")
92
93 def __repr__(self):
94 """Get a representation of the flight."""
95 s = "<Flight: %s-%s, %s, %s-%s," % (self.departureICAO,
96 self.arrivalICAO,
97 self.route,
98 self.departureTime, self.arrivalTime)
99 s += " %d %s," % (self.aircraftType, self.tailNumber)
100 s += " pax=%d, crew=%d, bag=%d, cargo=%d, mail=%d" % \
101 (self.numPassengers, self.numCrew,
102 self.bagWeight, self.cargoWeight, self.mailWeight)
103 s += ">"
104 return s
105
106#------------------------------------------------------------------------------
107
108class Plane(object):
109 """Information about an airplane in the fleet."""
110 def __init__(self, s):
111 """Build a plane info based on the given string.
112
113 The string consists of three, space-separated fields.
114 The first field is the tail number, the second field is the gate
115 number, the third field is the plane's status as a character."""
116 try:
117 words = s.split(" ")
118 tailNumber = words[0]
119 self.tailNumber = tailNumber
120
121 status = words[2] if len(words)>2 else None
122 self.status = const.PLANE_HOME if status=="H" else \
123 const.PLANE_AWAY if status=="A" else \
124 const.PLANE_PARKING if status=="P" else \
125 const.PLANE_UNKNOWN
126
127 gateNumber = words[1] if len(words)>1 else ""
128 self.gateNumber = gateNumber if gateNumber else None
129
130 except:
131 print >> sys.stderr, "Plane string is invalid: '" + s + "'"
132 self.tailNumber = None
133
134 def __repr__(self):
135 """Get the representation of the plane object."""
136 s = "<Plane: %s %s" % (self.tailNumber,
137 "home" if self.status==const.PLANE_HOME else \
138 "away" if self.status==const.PLANE_AWAY else \
139 "parking" if self.status==const.PLANE_PARKING \
140 else "unknown")
141 if self.gateNumber is not None:
142 s += " (gate " + self.gateNumber + ")"
143 s += ">"
144 return s
145
146
147#------------------------------------------------------------------------------
148
149class Fleet(object):
150 """Information about the whole fleet."""
151 def __init__(self, f):
152 """Construct the fleet information by reading the given file object."""
153 self._planes = {}
154 while True:
155 line = readline(f)
156 if not line or line == "#END": break
157
158 plane = Plane(line)
159 if plane.tailNumber is not None:
160 self._planes[plane.tailNumber] = plane
161
162 def isGateConflicting(self, plane):
163 """Check if the gate of the given plane conflicts with another plane's
164 position."""
165 for p in self._planes.itervalues():
166 if p.tailNumber!=plane.tailNumber and \
167 p.status==const.PLANE_HOME and \
168 p.gateNumber==plane.gateNumber:
169 return True
170
171 return False
172
173 def getOccupiedGateNumbers(self):
174 """Get a set containing the numbers of the gates occupied by planes."""
175 gateNumbers = set()
176 for p in self._planes.itervalues():
177 if p.status==const.PLANE_HOME and p.gateNumber:
178 gateNumbers.add(p.gateNumber)
179 return gateNumbers
180
181 def __getitem__(self, tailNumber):
182 """Get the plane with the given tail number.
183
184 If the plane is not in the fleet, None is returned."""
185 return self._planes[tailNumber] if tailNumber in self._planes else None
186
187 def __repr__(self):
188 """Get the representation of the fleet object."""
189 return self._planes.__repr__()
190
191#------------------------------------------------------------------------------
192
193class NOTAM(object):
194 """A NOTAM for an airport."""
195 def __init__(self, begin, notice, end = None, permanent = False,
196 repeatCycle = None):
197 """Construct the NOTAM."""
198 self.begin = begin
199 self.notice = notice
200 self.end = end
201 self.permanent = permanent
202 self.repeatCycle = None
203
204 def __repr__(self):
205 """Get the representation of the NOTAM."""
206 s = "<NOTAM " + str(self.begin)
207 if self.end:
208 s += " - " + str(self.end)
209 elif self.permanent:
210 s += " - PERMANENT"
211 if self.repeatCycle:
212 s += " (" + self.repeatCycle + ")"
213 s += ": " + self.notice
214 s += ">"
215 return s
216
217#------------------------------------------------------------------------------
218
219class NOTAMHandler(xml.sax.handler.ContentHandler):
220 """A handler for the NOTAM database."""
221 def __init__(self, airportICAOs):
222 """Construct the handler for the airports with the given ICAO code."""
223 self._notams = {}
224 for icao in airportICAOs:
225 self._notams[icao] = []
226
227 def startElement(self, name, attrs):
228 """Start an element."""
229 if name!="notam" or \
230 "A" not in attrs or not attrs["A"] or \
231 "B" not in attrs or not attrs["B"] or \
232 "E" not in attrs or not attrs["E"]:
233 return
234
235 icao = attrs["A"]
236 if icao not in self._notams:
237 return
238
239 begin = datetime.datetime.strptime(attrs["B"], "%Y-%m-%d %H:%M:%S")
240
241 c = attrs["C"] if "C" in attrs else None
242 end = datetime.datetime.strptime(c, "%Y-%m-%d %H:%M:%S") if c else None
243
244 permanent = attrs["C_flag"]=="PERM" if "C_flag" in attrs else False
245
246 repeatCycle = attrs["D"] if "D" in attrs else None
247
248 self._notams[icao].append(NOTAM(begin, attrs["E"], end = end,
249 permanent = permanent,
250 repeatCycle = repeatCycle))
251
252 def get(self, icao):
253 """Get the NOTAMs for the given ICAO code."""
254 return self._notams[icao] if icao in self._notams else []
255
256#------------------------------------------------------------------------------
257
258class Result(object):
259 """A result object.
260
261 An instance of this filled with the appropriate data is passed to the
262 callback function on each request."""
263
264 def __repr__(self):
265 """Get a representation of the result."""
266 s = "<Result:"
267 for (key, value) in self.__dict__.iteritems():
268 s += " " + key + "=" + unicode(value)
269 s += ">"
270 return s
271
272#------------------------------------------------------------------------------
273
274class Request(object):
275 """Base class for requests.
276
277 It handles any exceptions and the calling of the callback.
278
279 If an exception occurs during processing, the callback is called with
280 the two parameters: a boolean value of False, and the exception object.
281
282 If no exception occurs, the callback is called with True and the return
283 value of the run() function.
284
285 If the callback function throws an exception, that is caught and logged
286 to the debug log."""
287 def __init__(self, callback):
288 """Construct the request."""
289 self._callback = callback
290
291 def perform(self):
292 """Perform the request.
293
294 The object's run() function is called. If it throws an exception,
295 the callback is called with False, and the exception. Otherwise the
296 callback is called with True and the return value of the run()
297 function. Any exceptions thrown by the callback are caught and
298 reported."""
299 try:
300 result = self.run()
301 returned = True
302 except Exception, e:
303 result = e
304 returned = False
305
306 try:
307 self._callback(returned, result)
308 except Exception, e:
309 print >> sys.stderr, "web.Handler.Request.perform: callback throwed an exception: " + str(e)
310
311#------------------------------------------------------------------------------
312
313class Login(Request):
314 """A login request."""
315 iso88592decoder = codecs.getdecoder("iso-8859-2")
316
317 def __init__(self, callback, pilotID, password):
318 """Construct the login request with the given pilot ID and
319 password."""
320 super(Login, self).__init__(callback)
321
322 self._pilotID = pilotID
323 self._password = password
324
325 def run(self):
326 """Perform the login request."""
327 md5 = hashlib.md5()
328 md5.update(self._pilotID)
329 pilotID = md5.hexdigest()
330
331 md5 = hashlib.md5()
332 md5.update(self._password)
333 password = md5.hexdigest()
334
335 url = "http://www.virtualairlines.hu/leker2.php?pid=%s&psw=%s" % \
336 (pilotID, password)
337
338 result = Result()
339
340 f = urllib2.urlopen(url, timeout = 10.0)
341
342 status = readline(f)
343 result.loggedIn = status == ".OK."
344
345 if result.loggedIn:
346 result.pilotName = self.iso88592decoder(readline(f))[0]
347 result.exams = readline(f)
348 result.flights = []
349
350 while True:
351 line = readline(f)
352 if not line or line == "#ENDPIREP": break
353
354 flight = BookedFlight(line, f)
355 result.flights.append(flight)
356
357 result.flights.sort(cmp = lambda flight1, flight2:
358 cmp(flight1.departureTime,
359 flight2.departureTime))
360
361 f.close()
362
363 return result
364
365#------------------------------------------------------------------------------
366
367class GetFleet(Request):
368 """Request to get the fleet from the website."""
369
370 def __init__(self, callback):
371 """Construct the fleet request."""
372 super(GetFleet, self).__init__(callback)
373
374 def run(self):
375 """Perform the login request."""
376 url = "http://www.virtualairlines.hu/onlinegates_get.php"
377
378 f = urllib2.urlopen(url, timeout = 10.0)
379 result = Result()
380 result.fleet = Fleet(f)
381 f.close()
382
383 return result
384
385#------------------------------------------------------------------------------
386
387class UpdatePlane(Request):
388 """Update the status of one of the planes in the fleet."""
389 def __init__(self, callback, tailNumber, status, gateNumber = None):
390 """Construct the request."""
391 super(UpdatePlane, self).__init__(callback)
392 self._tailNumber = tailNumber
393 self._status = status
394 self._gateNumber = gateNumber
395
396 def run(self):
397 """Perform the plane update."""
398 url = "http://www.virtualairlines.hu/onlinegates_set.php"
399
400 status = "H" if self._status==const.PLANE_HOME else \
401 "A" if self._status==const.PLANE_AWAY else \
402 "P" if self._status==const.PLANE_PARKING else ""
403
404 gateNumber = self._gateNumber if self._gateNumber else ""
405
406 data = urllib.urlencode([("lajstrom", self._tailNumber),
407 ("status", status),
408 ("kapu", gateNumber)])
409
410 f = urllib2.urlopen(url, data, timeout = 10.0)
411 line = readline(f)
412
413 result = Result()
414 result.success = line == "OK"
415
416 return result
417
418#------------------------------------------------------------------------------
419
420class GetNOTAMs(Request):
421 """Get the NOTAMs from EURoutePro and select the ones we are interested
422 in."""
423 def __init__(self, callback, departureICAO, arrivalICAO):
424 """Construct the request for the given airports."""
425 super(GetNOTAMs, self).__init__(callback)
426 self._departureICAO = departureICAO
427 self._arrivalICAO = arrivalICAO
428
429 def run(self):
430 """Perform the retrieval of the NOTAMs."""
431 xmlParser = xml.sax.make_parser()
432 notamHandler = NOTAMHandler([self._departureICAO, self._arrivalICAO])
433 xmlParser.setContentHandler(notamHandler)
434
435 url = "http://notams.euroutepro.com/notams.xml"
436
437 f = urllib2.urlopen(url, timeout = 10.0)
438 try:
439 xmlParser.parse(f)
440 finally:
441 f.close()
442
443 result = Result()
444 result.departureNOTAMs = notamHandler.get(self._departureICAO)
445 result.arrivalNOTAMs = notamHandler.get(self._arrivalICAO)
446
447 return result
448
449#------------------------------------------------------------------------------
450
451class GetMETARs(Request):
452 """Get the METARs from the NOAA website for certain airport ICAOs."""
453
454 def __init__(self, callback, airports):
455 """Construct the request for the given airports."""
456 super(GetMETARs, self).__init__(callback)
457 self._airports = airports
458
459 def run(self):
460 """Perform the retrieval opf the METARs."""
461 url = "http://www.aviationweather.gov/adds/dataserver_current/httpparam?"
462 data = urllib.urlencode([ ("dataSource" , "metars"),
463 ("requestType", "retrieve"),
464 ("format", "csv"),
465 ("stationString", " ".join(self._airports)),
466 ("hoursBeforeNow", "24"),
467 ("mostRecentForEachStation", "constraint")])
468 url += data
469 f = urllib2.urlopen(url, timeout = 10.0)
470 try:
471 result = Result()
472 result.metars = {}
473 for line in iter(f.readline, ""):
474 if len(line)>5 and line[4]==' ':
475 icao = line[0:4]
476 if icao in self._airports:
477 result.metars[icao] = line.strip().split(",")[0]
478 finally:
479 f.close()
480
481 return result
482
483#------------------------------------------------------------------------------
484
485class SendPIREP(Request):
486 """A request to send a PIREP to the MAVA website."""
487 _flightTypes = { const.FLIGHTTYPE_SCHEDULED : "SCHEDULED",
488 const.FLIGHTTYPE_OLDTIMER : "OT",
489 const.FLIGHTTYPE_VIP : "VIP",
490 const.FLIGHTTYPE_CHARTER : "CHARTER" }
491
492 _latin2Encoder = codecs.getencoder("iso-8859-2")
493
494 def __init__(self, callback, pirep):
495 """Construct the request for the given PIREP."""
496 super(SendPIREP, self).__init__(callback)
497 self._pirep = pirep
498
499 def run(self):
500 """Perform the retrieval opf the METARs."""
501 url = "http://www.virtualairlines.hu/malevacars.php"
502
503 pirep = self._pirep
504
505 data = {}
506 data["acarsdata"] = pirep.getACARSText()
507
508 bookedFlight = pirep.bookedFlight
509 data["foglalas_id"] = bookedFlight.id
510 data["repdate"] = bookedFlight.departureTime.date().strftime("%Y-%m-%d")
511 data["fltnum"] = bookedFlight.callsign
512 data["depap"] = bookedFlight.departureICAO
513 data["arrap"] = bookedFlight.arrivalICAO
514 data["pass"] = str(bookedFlight.numPassengers)
515 data["crew"] = str(bookedFlight.numCrew)
516 data["cargo"] = str(pirep.cargoWeight)
517 data["bag"] = str(bookedFlight.bagWeight)
518 data["mail"] = str(bookedFlight.mailWeight)
519
520 data["flttype"] = SendPIREP._flightTypes[pirep.flightType]
521 data["onoff"] = "1" if pirep.online else "0"
522 data["bt_dep"] = util.getTimestampString(pirep.blockTimeStart)
523 data["bt_arr"] = util.getTimestampString(pirep.blockTimeEnd)
524 data["bt_dur"] = util.getTimeIntervalString(pirep.blockTimeEnd -
525 pirep.blockTimeStart)
526 data["ft_dep"] = util.getTimestampString(pirep.flightTimeStart)
527 data["ft_arr"] = util.getTimestampString(pirep.flightTimeEnd)
528 data["ft_dur"] = util.getTimeIntervalString(pirep.flightTimeEnd -
529 pirep.flightTimeStart)
530 data["timecomm"] = pirep.getTimeComment()
531 data["fuel"] = "%.0f" % (pirep.fuelUsed,)
532 data["dep_rwy"] = pirep.departureRunway
533 data["arr_rwy"] = pirep.arrivalRunway
534 data["wea_dep"] = pirep.departureMETAR
535 data["wea_arr"] = pirep.arrivalMETAR
536 data["alt"] = "FL%.0f" % (pirep.filedCruiseAltitude/100.0,)
537 if pirep.filedCruiseAltitude!=pirep.cruiseAltitude:
538 data["mod_alt"] = "FL%.0f" % (pirep.cruiseAltitude/100.0,)
539 else:
540 data["mod_alt"] = ""
541 data["sid"] = pirep.sid
542 data["navroute"] = pirep.route
543 data["star"] = pirep.getSTAR()
544 data["aprtype"] = pirep.approachType
545 data["diff"] = "2"
546 data["comment"] = SendPIREP._latin2Encoder(pirep.comments)[0]
547 data["flightdefect"] = SendPIREP._latin2Encoder(pirep.flightDefects)[0]
548 data["kritika"] = pirep.getRatingText()
549 data["flightRating"] = "%.1f" % (max(0.0, pirep.rating),)
550 data["distance"] = "%.3f" % (pirep.flownDistance,)
551 data["insdate"] = datetime.date.today().strftime("%Y-%m-%d")
552
553 f = urllib2.urlopen(url, urllib.urlencode(data), timeout = 10.0)
554 try:
555 result = Result()
556 line = f.readline().strip()
557 print "PIREP result from website:", line
558 result.success = line=="OK"
559 result.alreadyFlown = line=="MARVOLT"
560 result.notAvailable = line=="NOMORE"
561 finally:
562 f.close()
563
564 return result
565
566#------------------------------------------------------------------------------
567
568class Handler(threading.Thread):
569 """The handler for the web services.
570
571 It can process one request at a time. The results are passed to a callback
572 function."""
573 def __init__(self):
574 """Construct the handler."""
575 super(Handler, self).__init__()
576
577 self._requests = []
578 self._requestCondition = threading.Condition()
579
580 self.daemon = True
581
582 def login(self, callback, pilotID, password):
583 """Enqueue a login request."""
584 self._addRequest(Login(callback, pilotID, password))
585
586 def getFleet(self, callback):
587 """Enqueue a fleet retrieval request."""
588 self._addRequest(GetFleet(callback))
589
590 def updatePlane(self, callback, tailNumber, status, gateNumber = None):
591 """Update the status of the given plane."""
592 self._addRequest(UpdatePlane(callback, tailNumber, status, gateNumber))
593
594 def getNOTAMs(self, callback, departureICAO, arrivalICAO):
595 """Get the NOTAMs for the given two airports."""
596 self._addRequest(GetNOTAMs(callback, departureICAO, arrivalICAO))
597
598 def getMETARs(self, callback, airports):
599 """Get the METARs for the given airports."""
600 self._addRequest(GetMETARs(callback, airports))
601
602 def sendPIREP(self, callback, pirep):
603 """Send the given PIREP."""
604 self._addRequest(SendPIREP(callback, pirep))
605
606 def run(self):
607 """Process the requests."""
608 while True:
609 with self._requestCondition:
610 while not self._requests:
611 self._requestCondition.wait()
612 request = self._requests[0]
613 del self._requests[0]
614
615 request.perform()
616
617 def _addRequest(self, request):
618 """Add the given request to the queue."""
619 with self._requestCondition:
620 self._requests.append(request)
621 self._requestCondition.notify()
622
623#------------------------------------------------------------------------------
624
625if __name__ == "__main__":
626 import time
627
628 def callback(returned, result):
629 print returned, unicode(result)
630
631 handler = Handler()
632 handler.start()
633
634 #handler.login(callback, "P096", "V5fwj")
635 #handler.getFleet(callback)
636 # Plane: HA-LEG home (gate 67)
637 #handler.updatePlane(callback, "HA-LQC", const.PLANE_AWAY, "72")
638 #time.sleep(3)
639 #handler.getFleet(callback)
640 #time.sleep(3)
641
642 #handler.getNOTAMs(callback, "LHBP", "EPWA")
643 handler.getMETARs(callback, ["LHBP", "EPWA"])
644 time.sleep(5)
645
646
647#------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.