source: src/mlx/web.py@ 136:6d206b573dee

Last change on this file since 136:6d206b573dee was 130:f3b2a892af5a, checked in by István Váradi <ivaradi@…>, 13 years ago

Plane position updating works

File size: 23.3 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 updatePlane(self, tailNumber, status, gateNumber = None):
183 """Update the status of the given plane."""
184 if tailNumber in self._planes:
185 plane = self._planes[tailNumber]
186 plane.status = status
187 plane.gateNumber = gateNumber
188
189 def __iter__(self):
190 """Get an iterator over the planes."""
191 for plane in self._planes.itervalues():
192 yield plane
193
194 def __getitem__(self, tailNumber):
195 """Get the plane with the given tail number.
196
197 If the plane is not in the fleet, None is returned."""
198 return self._planes[tailNumber] if tailNumber in self._planes else None
199
200 def __repr__(self):
201 """Get the representation of the fleet object."""
202 return self._planes.__repr__()
203
204#------------------------------------------------------------------------------
205
206class NOTAM(object):
207 """A NOTAM for an airport."""
208 def __init__(self, begin, notice, end = None, permanent = False,
209 repeatCycle = None):
210 """Construct the NOTAM."""
211 self.begin = begin
212 self.notice = notice
213 self.end = end
214 self.permanent = permanent
215 self.repeatCycle = None
216
217 def __repr__(self):
218 """Get the representation of the NOTAM."""
219 s = "<NOTAM " + str(self.begin)
220 if self.end:
221 s += " - " + str(self.end)
222 elif self.permanent:
223 s += " - PERMANENT"
224 if self.repeatCycle:
225 s += " (" + self.repeatCycle + ")"
226 s += ": " + self.notice
227 s += ">"
228 return s
229
230#------------------------------------------------------------------------------
231
232class NOTAMHandler(xml.sax.handler.ContentHandler):
233 """A handler for the NOTAM database."""
234 def __init__(self, airportICAOs):
235 """Construct the handler for the airports with the given ICAO code."""
236 self._notams = {}
237 for icao in airportICAOs:
238 self._notams[icao] = []
239
240 def startElement(self, name, attrs):
241 """Start an element."""
242 if name!="notam" or \
243 "A" not in attrs or not attrs["A"] or \
244 "B" not in attrs or not attrs["B"] or \
245 "E" not in attrs or not attrs["E"]:
246 return
247
248 icao = attrs["A"]
249 if icao not in self._notams:
250 return
251
252 begin = datetime.datetime.strptime(attrs["B"], "%Y-%m-%d %H:%M:%S")
253
254 c = attrs["C"] if "C" in attrs else None
255 end = datetime.datetime.strptime(c, "%Y-%m-%d %H:%M:%S") if c else None
256
257 permanent = attrs["C_flag"]=="PERM" if "C_flag" in attrs else False
258
259 repeatCycle = attrs["D"] if "D" in attrs else None
260
261 self._notams[icao].append(NOTAM(begin, attrs["E"], end = end,
262 permanent = permanent,
263 repeatCycle = repeatCycle))
264
265 def get(self, icao):
266 """Get the NOTAMs for the given ICAO code."""
267 return self._notams[icao] if icao in self._notams else []
268
269#------------------------------------------------------------------------------
270
271class Result(object):
272 """A result object.
273
274 An instance of this filled with the appropriate data is passed to the
275 callback function on each request."""
276
277 def __repr__(self):
278 """Get a representation of the result."""
279 s = "<Result:"
280 for (key, value) in self.__dict__.iteritems():
281 s += " " + key + "=" + unicode(value)
282 s += ">"
283 return s
284
285#------------------------------------------------------------------------------
286
287class Request(object):
288 """Base class for requests.
289
290 It handles any exceptions and the calling of the callback.
291
292 If an exception occurs during processing, the callback is called with
293 the two parameters: a boolean value of False, and the exception object.
294
295 If no exception occurs, the callback is called with True and the return
296 value of the run() function.
297
298 If the callback function throws an exception, that is caught and logged
299 to the debug log."""
300 def __init__(self, callback):
301 """Construct the request."""
302 self._callback = callback
303
304 def perform(self):
305 """Perform the request.
306
307 The object's run() function is called. If it throws an exception,
308 the callback is called with False, and the exception. Otherwise the
309 callback is called with True and the return value of the run()
310 function. Any exceptions thrown by the callback are caught and
311 reported."""
312 try:
313 result = self.run()
314 returned = True
315 except Exception, e:
316 traceback.print_exc()
317 result = e
318 returned = False
319
320 try:
321 self._callback(returned, result)
322 except Exception, e:
323 print >> sys.stderr, "web.Handler.Request.perform: callback throwed an exception: " + str(e)
324 traceback.print_exc()
325
326#------------------------------------------------------------------------------
327
328class Login(Request):
329 """A login request."""
330 iso88592decoder = codecs.getdecoder("iso-8859-2")
331
332 def __init__(self, callback, pilotID, password):
333 """Construct the login request with the given pilot ID and
334 password."""
335 super(Login, self).__init__(callback)
336
337 self._pilotID = pilotID
338 self._password = password
339
340 def run(self):
341 """Perform the login request."""
342 md5 = hashlib.md5()
343 md5.update(self._pilotID)
344 pilotID = md5.hexdigest()
345
346 md5 = hashlib.md5()
347 md5.update(self._password)
348 password = md5.hexdigest()
349
350 url = "http://www.virtualairlines.hu/leker2.php?pid=%s&psw=%s" % \
351 (pilotID, password)
352
353 result = Result()
354
355 f = urllib2.urlopen(url, timeout = 10.0)
356
357 status = readline(f)
358 result.loggedIn = status == ".OK."
359
360 if result.loggedIn:
361 result.pilotName = self.iso88592decoder(readline(f))[0]
362 result.exams = readline(f)
363 result.flights = []
364
365 while True:
366 line = readline(f)
367 if not line or line == "#ENDPIREP": break
368
369 flight = BookedFlight(line, f)
370 result.flights.append(flight)
371
372 result.flights.sort(cmp = lambda flight1, flight2:
373 cmp(flight1.departureTime,
374 flight2.departureTime))
375
376 f.close()
377
378 return result
379
380#------------------------------------------------------------------------------
381
382class GetFleet(Request):
383 """Request to get the fleet from the website."""
384
385 def __init__(self, callback):
386 """Construct the fleet request."""
387 super(GetFleet, self).__init__(callback)
388
389 def run(self):
390 """Perform the login request."""
391 url = "http://www.virtualairlines.hu/onlinegates_get.php"
392
393 f = urllib2.urlopen(url, timeout = 10.0)
394 result = Result()
395 result.fleet = Fleet(f)
396 f.close()
397
398 return result
399
400#------------------------------------------------------------------------------
401
402class UpdatePlane(Request):
403 """Update the status of one of the planes in the fleet."""
404 def __init__(self, callback, tailNumber, status, gateNumber = None):
405 """Construct the request."""
406 super(UpdatePlane, self).__init__(callback)
407 self._tailNumber = tailNumber
408 self._status = status
409 self._gateNumber = gateNumber
410
411 def run(self):
412 """Perform the plane update."""
413 url = "http://www.virtualairlines.hu/onlinegates_set.php"
414
415 status = "H" if self._status==const.PLANE_HOME else \
416 "A" if self._status==const.PLANE_AWAY else \
417 "P" if self._status==const.PLANE_PARKING else ""
418
419 gateNumber = self._gateNumber if self._gateNumber else ""
420
421 data = urllib.urlencode([("lajstrom", self._tailNumber),
422 ("status", status),
423 ("kapu", gateNumber)])
424
425 f = urllib2.urlopen(url, data, timeout = 10.0)
426 line = readline(f)
427
428 result = Result()
429 result.success = line == "OK"
430
431 return result
432
433#------------------------------------------------------------------------------
434
435class GetNOTAMs(Request):
436 """Get the NOTAMs from EURoutePro and select the ones we are interested
437 in."""
438 def __init__(self, callback, departureICAO, arrivalICAO):
439 """Construct the request for the given airports."""
440 super(GetNOTAMs, self).__init__(callback)
441 self._departureICAO = departureICAO
442 self._arrivalICAO = arrivalICAO
443
444 def run(self):
445 """Perform the retrieval of the NOTAMs."""
446 xmlParser = xml.sax.make_parser()
447 notamHandler = NOTAMHandler([self._departureICAO, self._arrivalICAO])
448 xmlParser.setContentHandler(notamHandler)
449
450 url = "http://notams.euroutepro.com/notams.xml"
451
452 f = urllib2.urlopen(url, timeout = 10.0)
453 try:
454 xmlParser.parse(f)
455 finally:
456 f.close()
457
458 result = Result()
459 result.departureNOTAMs = notamHandler.get(self._departureICAO)
460 result.arrivalNOTAMs = notamHandler.get(self._arrivalICAO)
461
462 return result
463
464#------------------------------------------------------------------------------
465
466class GetMETARs(Request):
467 """Get the METARs from the NOAA website for certain airport ICAOs."""
468
469 def __init__(self, callback, airports):
470 """Construct the request for the given airports."""
471 super(GetMETARs, self).__init__(callback)
472 self._airports = airports
473
474 def run(self):
475 """Perform the retrieval opf the METARs."""
476 url = "http://www.aviationweather.gov/adds/dataserver_current/httpparam?"
477 data = urllib.urlencode([ ("dataSource" , "metars"),
478 ("requestType", "retrieve"),
479 ("format", "csv"),
480 ("stationString", " ".join(self._airports)),
481 ("hoursBeforeNow", "24"),
482 ("mostRecentForEachStation", "constraint")])
483 url += data
484 f = urllib2.urlopen(url, timeout = 10.0)
485 try:
486 result = Result()
487 result.metars = {}
488 for line in iter(f.readline, ""):
489 if len(line)>5 and line[4]==' ':
490 icao = line[0:4]
491 if icao in self._airports:
492 result.metars[icao] = line.strip().split(",")[0]
493 finally:
494 f.close()
495
496 return result
497
498#------------------------------------------------------------------------------
499
500class SendPIREP(Request):
501 """A request to send a PIREP to the MAVA website."""
502 _flightTypes = { const.FLIGHTTYPE_SCHEDULED : "SCHEDULED",
503 const.FLIGHTTYPE_OLDTIMER : "OT",
504 const.FLIGHTTYPE_VIP : "VIP",
505 const.FLIGHTTYPE_CHARTER : "CHARTER" }
506
507 _latin2Encoder = codecs.getencoder("iso-8859-2")
508
509 def __init__(self, callback, pirep):
510 """Construct the request for the given PIREP."""
511 super(SendPIREP, self).__init__(callback)
512 self._pirep = pirep
513
514 def run(self):
515 """Perform the retrieval opf the METARs."""
516 url = "http://www.virtualairlines.hu/malevacars.php"
517
518 pirep = self._pirep
519
520 data = {}
521 data["acarsdata"] = pirep.getACARSText()
522
523 bookedFlight = pirep.bookedFlight
524 data["foglalas_id"] = bookedFlight.id
525 data["repdate"] = bookedFlight.departureTime.date().strftime("%Y-%m-%d")
526 data["fltnum"] = bookedFlight.callsign
527 data["depap"] = bookedFlight.departureICAO
528 data["arrap"] = bookedFlight.arrivalICAO
529 data["pass"] = str(bookedFlight.numPassengers)
530 data["crew"] = str(bookedFlight.numCrew)
531 data["cargo"] = str(pirep.cargoWeight)
532 data["bag"] = str(bookedFlight.bagWeight)
533 data["mail"] = str(bookedFlight.mailWeight)
534
535 data["flttype"] = SendPIREP._flightTypes[pirep.flightType]
536 data["onoff"] = "1" if pirep.online else "0"
537 data["bt_dep"] = util.getTimestampString(pirep.blockTimeStart)
538 data["bt_arr"] = util.getTimestampString(pirep.blockTimeEnd)
539 data["bt_dur"] = util.getTimeIntervalString(pirep.blockTimeEnd -
540 pirep.blockTimeStart)
541 data["ft_dep"] = util.getTimestampString(pirep.flightTimeStart)
542 data["ft_arr"] = util.getTimestampString(pirep.flightTimeEnd)
543 data["ft_dur"] = util.getTimeIntervalString(pirep.flightTimeEnd -
544 pirep.flightTimeStart)
545 data["timecomm"] = pirep.getTimeComment()
546 data["fuel"] = "%.0f" % (pirep.fuelUsed,)
547 data["dep_rwy"] = pirep.departureRunway
548 data["arr_rwy"] = pirep.arrivalRunway
549 data["wea_dep"] = pirep.departureMETAR
550 data["wea_arr"] = pirep.arrivalMETAR
551 data["alt"] = "FL%.0f" % (pirep.filedCruiseAltitude/100.0,)
552 if pirep.filedCruiseAltitude!=pirep.cruiseAltitude:
553 data["mod_alt"] = "FL%.0f" % (pirep.cruiseAltitude/100.0,)
554 else:
555 data["mod_alt"] = ""
556 data["sid"] = pirep.sid
557 data["navroute"] = pirep.route
558 data["star"] = pirep.getSTAR()
559 data["aprtype"] = pirep.approachType
560 data["diff"] = "2"
561 data["comment"] = SendPIREP._latin2Encoder(pirep.comments)[0]
562 data["flightdefect"] = SendPIREP._latin2Encoder(pirep.flightDefects)[0]
563 data["kritika"] = pirep.getRatingText()
564 data["flightrating"] = "%.1f" % (max(0.0, pirep.rating),)
565 data["distance"] = "%.3f" % (pirep.flownDistance,)
566 data["insdate"] = datetime.date.today().strftime("%Y-%m-%d")
567
568 f = urllib2.urlopen(url, urllib.urlencode(data), timeout = 10.0)
569 try:
570 result = Result()
571 line = f.readline().strip()
572 print "PIREP result from website:", line
573 result.success = line=="OK"
574 result.alreadyFlown = line=="MARVOLT"
575 result.notAvailable = line=="NOMORE"
576 finally:
577 f.close()
578
579 return result
580
581#------------------------------------------------------------------------------
582
583class Handler(threading.Thread):
584 """The handler for the web services.
585
586 It can process one request at a time. The results are passed to a callback
587 function."""
588 def __init__(self):
589 """Construct the handler."""
590 super(Handler, self).__init__()
591
592 self._requests = []
593 self._requestCondition = threading.Condition()
594
595 self.daemon = True
596
597 def login(self, callback, pilotID, password):
598 """Enqueue a login request."""
599 self._addRequest(Login(callback, pilotID, password))
600
601 def getFleet(self, callback):
602 """Enqueue a fleet retrieval request."""
603 self._addRequest(GetFleet(callback))
604
605 def updatePlane(self, callback, tailNumber, status, gateNumber = None):
606 """Update the status of the given plane."""
607 self._addRequest(UpdatePlane(callback, tailNumber, status, gateNumber))
608
609 def getNOTAMs(self, callback, departureICAO, arrivalICAO):
610 """Get the NOTAMs for the given two airports."""
611 self._addRequest(GetNOTAMs(callback, departureICAO, arrivalICAO))
612
613 def getMETARs(self, callback, airports):
614 """Get the METARs for the given airports."""
615 self._addRequest(GetMETARs(callback, airports))
616
617 def sendPIREP(self, callback, pirep):
618 """Send the given PIREP."""
619 self._addRequest(SendPIREP(callback, pirep))
620
621 def run(self):
622 """Process the requests."""
623 while True:
624 with self._requestCondition:
625 while not self._requests:
626 self._requestCondition.wait()
627 request = self._requests[0]
628 del self._requests[0]
629
630 request.perform()
631
632 def _addRequest(self, request):
633 """Add the given request to the queue."""
634 with self._requestCondition:
635 self._requests.append(request)
636 self._requestCondition.notify()
637
638#------------------------------------------------------------------------------
639
640if __name__ == "__main__":
641 import time
642
643 def callback(returned, result):
644 print returned, unicode(result)
645
646 handler = Handler()
647 handler.start()
648
649 #handler.login(callback, "P096", "V5fwj")
650 #handler.getFleet(callback)
651 # Plane: HA-LEG home (gate 67)
652 #handler.updatePlane(callback, "HA-LQC", const.PLANE_AWAY, "72")
653 #time.sleep(3)
654 #handler.getFleet(callback)
655 #time.sleep(3)
656
657 #handler.getNOTAMs(callback, "LHBP", "EPWA")
658 handler.getMETARs(callback, ["LHBP", "EPWA"])
659 time.sleep(5)
660
661
662#------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.