source: src/mlx/web.py@ 115:fa8178825b29

Last change on this file since 115:fa8178825b29 was 106:5d81096406c0, checked in by István Váradi <ivaradi@…>, 13 years ago

The METAR of the arrival airport is downloaded in the landing stage.

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