source: src/mlx/web.py@ 66:d288a492befe

Last change on this file since 66:d288a492befe was 66:d288a492befe, checked in by István Váradi <ivaradi@…>, 12 years ago

The METARs can now be downloaded

File size: 18.7 KB
Line 
1# Interface towards the websites used
2
3#------------------------------------------------------------------------------
4
5import const
6
7import threading
8import sys
9import urllib
10import urllib2
11import hashlib
12import time
13import datetime
14import codecs
15import re
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)
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)
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)
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)
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 # Regular expression matching a METAR line
455 metarLine = re.compile("^[A-Z0-9]{4} ")
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 tm = time.gmtime()
465 url = "http://weather.noaa.gov/pub/data/observations/metar/cycles/%02dZ.TXT" % \
466 (tm.tm_hour,)
467 f = urllib2.urlopen(url)
468 try:
469 result = Result()
470 result.metars = {}
471 for line in iter(f.readline, ""):
472 if len(line)>5 and line[4]==' ':
473 icao = line[0:4]
474 if icao in self._airports:
475 result.metars[icao] = line.strip()
476 finally:
477 f.close()
478
479 return result
480
481#------------------------------------------------------------------------------
482
483class Handler(threading.Thread):
484 """The handler for the web services.
485
486 It can process one request at a time. The results are passed to a callback
487 function."""
488 def __init__(self):
489 """Construct the handler."""
490 super(Handler, self).__init__()
491
492 self._requests = []
493 self._requestCondition = threading.Condition()
494
495 self.daemon = True
496
497 def login(self, callback, pilotID, password):
498 """Enqueue a login request."""
499 self._addRequest(Login(callback, pilotID, password))
500
501 def getFleet(self, callback):
502 """Enqueue a fleet retrieval request."""
503 self._addRequest(GetFleet(callback))
504
505 def updatePlane(self, callback, tailNumber, status, gateNumber = None):
506 """Update the status of the given plane."""
507 self._addRequest(UpdatePlane(callback, tailNumber, status, gateNumber))
508
509 def getNOTAMs(self, callback, departureICAO, arrivalICAO):
510 """Get the NOTAMs for the given two airports."""
511 self._addRequest(GetNOTAMs(callback, departureICAO, arrivalICAO))
512
513 def getMETARs(self, callback, airports):
514 """Get the METARs for the given airports."""
515 self._addRequest(GetMETARs(callback, airports))
516
517 def run(self):
518 """Process the requests."""
519 while True:
520 with self._requestCondition:
521 while not self._requests:
522 self._requestCondition.wait()
523 request = self._requests[0]
524 del self._requests[0]
525
526 request.perform()
527
528 def _addRequest(self, request):
529 """Add the given request to the queue."""
530 with self._requestCondition:
531 self._requests.append(request)
532 self._requestCondition.notify()
533
534#------------------------------------------------------------------------------
535
536if __name__ == "__main__":
537 import time
538
539 def callback(returned, result):
540 print returned, unicode(result)
541
542 handler = Handler()
543 handler.start()
544
545 #handler.login(callback, "P096", "V5fwj")
546 #handler.getFleet(callback)
547 # Plane: HA-LEG home (gate 67)
548 #handler.updatePlane(callback, "HA-LQC", const.PLANE_AWAY, "72")
549 #time.sleep(3)
550 #handler.getFleet(callback)
551 #time.sleep(3)
552
553 #handler.getNOTAMs(callback, "LHBP", "EPWA")
554 handler.getMETARs(callback, ["LHBP", "EPWA"])
555 time.sleep(5)
556
557
558#------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.