source: core/trunk/client/adsb2nmea.py @ 271

Last change on this file since 271 was 271, checked in by dominic, 11 years ago

Added ADS-B Clients

File size: 10.0 KB
RevLine 
[271]1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3#
4
5from math import *
6import datetime
7import re
8import os
9from socket import socket, gethostbyname, getaddrinfo, error, \
10  AF_INET, SOCK_STREAM, SOCK_DGRAM
11from sys import argv, stderr
12from time import time
13
14NaN = float('nan')
15
16TypeADSB       = 1
17traffic_data = {}       # dictionary for all traffic
18
19class aircraft:
20    def __init__(self):
21        self.id = ""                # ICAO Id
22        self.latitude = self.longitude = 0.0
23        self.altitude = NaN         # Feet
24
25class traffic:
26    def __init__(self):
27        self.id = id                # ICAO Id
28        self.latitude = self.longitude = 0.0
29        self.altitude = NaN         # Feet
30        self.GS = 0                 # Ground speed
31        self.VR = 0                 # vertical rate
32        self.CS = ""                # call sign
33        self.TRK = 0                # Track
34        self.time = ""              # Time
35
36class adsb2udp():
37  "SBS client interface to ADS-B."
38  def __init__(self, src_host='192.168.178.21', src_port=30003, device='', \
39    my_Id='TEST', dst_host='192.168.2.108', dst_port=10110, \
40    base_lat=48.715247, base_long=11.533155, base_alt=400.0):
41    print 'adsb2udp: src host =' , src_host , ', src_port =' , src_port , \
42    ', dst_host =' , dst_host , ', dst_port =' , dst_port , ', base_lat =' , \
43    base_lat , ', base_long =' , base_long , ', base_alt =' , base_alt
44    self.verbose = 0
45    self.linebuffer = ""
46    self.response = ""
47    self.sock = None        # in case we blow up in connect
48    self.pos = aircraft()
49    self.device = device
50    self.src_host = gethostbyname(src_host)
51    self.src_port = src_port
52    self.dst_host = gethostbyname(dst_host)
53    self.dst_port = dst_port
54    self.base_lat = base_lat
55    self.base_long = base_long
56    self.base_alt = base_alt
57    if src_host != None:
58        self.connect(src_host, src_port)
59    self.d = socket(AF_INET, SOCK_DGRAM)
60
61  def connect(self, host, port):
62        """Connect to a host on a given port."""
63        msg = "getaddrinfo returns an empty list"
64        self.sock = None
65        for res in getaddrinfo(host, port, 0, SOCK_STREAM):
66                af, socktype, proto, canonname, sa = res
67                try:
68                        self.sock = socket(af, socktype, proto)
69                        #if self.debuglevel > 0: print 'connect:', (host, port)
70                        self.sock.connect(sa)
71                except error, msg:
72                        #if self.debuglevel > 0: print 'connect fail:', (host, port)
73                        self.close()
74                        continue
75                break
76        if not self.sock:
77                raise error, msg
78               
79        FIFO_PATH = '/tmp/my_fifo'
80        if os.path.exists(FIFO_PATH):
81                os.unlink(FIFO_PATH)
82
83        if not os.path.exists(FIFO_PATH):
84                os.mkfifo(FIFO_PATH)
85        self.my_fifo = open(FIFO_PATH, 'w+')
86        print "my_fifo:",self.my_fifo   
87
88  def close(self):
89      if self.sock:
90          self.sock.close()
91      self.sock = None
92
93  def __del__(self):
94      self.close()
95
96  def read(self):
97      "Wait for and read data being streamed from the daemon."
98      if self.verbose > 1:
99          stderr.write("poll: reading from daemon...\n")
100      eol = self.linebuffer.find('\n')
101      if eol == -1:
102         
103          frag = self.sock.recv(4096)
104          self.linebuffer += frag
105          if self.verbose > 1:
106              stderr.write("poll: read complete.\n")
107          if not self.linebuffer:
108              if self.verbose > 1:
109                  stderr.write("poll: returning -1.\n")
110              # Read failed
111              return -1
112          eol = self.linebuffer.find('\n')
113          if eol == -1:
114              if self.verbose > 1:
115                  stderr.write("poll: returning 0.\n")
116              # Read succeeded, but only got a fragment
117              return 0
118      else:
119          if self.verbose > 1:
120              stderr.write("poll: fetching from buffer.\n")
121
122      # We got a line
123      eol += 1
124      self.response = self.linebuffer[:eol]
125      self.linebuffer = self.linebuffer[eol:]
126
127      # Can happen if daemon terminates while we're reading.
128      if not self.response:
129          return -1
130      if self.verbose:
131          stderr.write("poll: data is %s\n" % repr(self.response))
132      self.received = time()
133      # We got a \n-terminated line
134      return len(self.response)
135
136  def unpack(self, buf):
137      fields = buf.strip().split(",")
138      # print fields
139      if fields[0] == "MSG" and fields[1] == "3":
140        # print fields
141        self.pos.id = fields[4]
142        self.latitude = self.longitude = 0.0
143        self.altitude = NaN         # Feet
144        if fields[11] != '':
145          self.pos.altitude = float(fields[11])
146        if fields[14] != '':
147          self.pos.latitude = float(fields[14])
148        if fields[15] != '':
149          self.pos.longitude = float(fields[15])
150
151      if fields[0] == "MSG" and fields[1] == "1":
152        # Call sign
153        # fields[1] = traffic(fields[4])
154        print fields
155               
156  def checksum(self, sentence):
157 
158      """ Remove leading $ """
159      sentence = sentence.lstrip('$')
160 
161      nmeadata,cksum = re.split('\*', sentence)
162      #print nmeadata
163      calc_cksum = 0
164      for s in nmeadata:
165          calc_cksum ^= ord(s)
166 
167      return calc_cksum
168
169
170  def process(self):
171    "Process one incoming ADS-B message into one outgoing UDP packet."
172
173    try:
174      while True:
175 
176        self.read()
177
178        # stderr.write("poll: data is %s\n" % repr(self.response))
179        # if self.response.startswith("MSG,1") :
180            # print 'Callsign'
181            # print self.response
182            # self.unpack(self.response)
183        # if self.response.startswith("MSG,4") :
184            # print 'GS,TRK,VR'
185            # print self.response
186            # self.unpack(self.response)
187        # if self.response.startswith("MSG,2") :
188            # print 'Alt, GS, Trk, Lat, Lng, GND'
189            # print self.response
190        if self.response.startswith("MSG,3") :
191            self.unpack(self.response)
192            #print '----------------------------------------'
193            #print ' ADS-B reading'
194            #print '----------------------------------------'
195            #print 'id          ' , self.pos.id
196            #print 'latitude    ' , self.pos.latitude
197            #print 'longitude   ' , self.pos.longitude
198            #print 'altitude    ' , self.pos.altitude
199
200            buf = "%s %s %f %f %.1f %.1f %.1f %u" % ( 0, self.pos.id, \
201              self.pos.latitude, self.pos.longitude, self.pos.altitude / 3.2808, \
202              0, 0 , TypeADSB )
203         
204            lon1, lat1, lon2, lat2 = map(radians, [self.base_long, self.base_lat, self.pos.longitude, self.pos.latitude])
205         
206            dlon = lon2 - lon1
207            dlat = lat2 - lat1
208            a = sin(dlat/2)**2 + cos(lat1) * cos(lat2) * sin(dlon/2)**2
209            c = 2 * atan2(sqrt(a), sqrt(1-a))
210            Distance = 6371000 * c
211
212            Bearing = atan2(sin(lon2-lon1)*cos(lat2), cos(lat1)*sin(lat2)-sin(lat1)*cos(lat2)*cos(lon2-lon1))             
213            relBearing = degrees(Bearing)
214            absBearing = (relBearing + 360) % 360
215            #stderr.write("Bearing %f ° " % Bearing)
216            #stderr.write("Distance %f km \n" % Distance)
217         
218            relNorth = cos(radians(absBearing))*Distance
219            relEast = sin(radians(absBearing))*Distance
220            relVert = self.pos.altitude/ 3.2808 - self.base_alt
221         
222            #tip: it is necessary to supply both $PFLAU and $PFLAA Flarm(R) NMEA messages to XCSoar, otherwise it does not not show the traffic.
223            #PFLAA,<AlarmLevel>,<RelativeNorth>,<RelativeEast>,<RelativeVertical>,<IDType>,<ID>,<Track>,<TurnRate>,<GroundSpeed>,<ClimbRate>,<AcftType>
224            #PFLAU,<RX>,<TX>,<GPS>,<Power>,<AlarmLevel>,<RelativeBearing>,<AlarmType>,<RelativeVertical>,<RelativeDistance>(,<ID>)
225           
226            str1 = "$PFLAU,,,,0,%d,0,%d,%u,%s*" % \
227            ( relBearing, int(relVert), int(Distance), self.pos.id )
228            csum = self.checksum(str1)
229            str1 += "%02x\r\n" % csum
230            #stderr.write(str1)
231            # XCSoar ignores warning data from PFLAU
232            # $PFLAA,0,1671,186,410,2,DDAA30,252,,21,12.4,1*62
233            str2 = "$PFLAA,0,%d,%d,%d,1,%s,180,0,999,0,8*" % \
234            ( int(relNorth), int(relEast), int(relVert), self.pos.id )
235            csum = self.checksum(str2)
236            str2 += "%02x\r\n" % csum
237            #stderr.write(str2)
238           
239            if (self.base_lat < 0) :
240                Latstring = "S"
241            else :
242                Latstring = "N"
243            if (self.base_long < 0) :
244                Longstring = "W"
245            else :
246                Longstring = "E"
247            latdeg = int(abs(floor(self.base_lat)))
248            latmin = abs(60*(self.base_lat-latdeg))
249            longdeg = int(abs(floor(self.base_long)))
250            longmin = abs(60*(self.base_long-longdeg))
251            #--GLL Latitude,N/S,Longitude,E/W,UTC,Status (A=Valid, V=Invalid),Checksum
252            #GPRMC,225446,A,4916.45,N,12311.12,W,000.5,054.7,191194,020.3,E*68
253            now = datetime.datetime.utcnow()
254            str3 = "$GPRMC,%02i%02i%02i,A,%s%.2f,%s,%s%.2f,%s,,,%02i%02i%02i,001.0,W*" % \
255            (now.hour, now.minute, now.second, latdeg, latmin, Latstring, longdeg, longmin, Longstring, now.day, now.month, now.year )
256            csum = self.checksum(str3)
257            str3 += "%02x\r\n" % csum
258           
259            #--- GPGGA sentences - required for flarmradar.ch
260            # $GPGGA,173431.00,4934.90002,N,00924.13332,E,7,5,0.96,415.1,M,48.0,M,,*60
261            str4 = "$GPGGA,%02i%02i%02i.00,%02d%.5f,%s,%03d%.5f,%s,7,5,0.96,415.1,M,48.0,M,,*" % \
262            (now.hour, now.minute, now.second, latdeg, latmin, Latstring, longdeg, longmin, Longstring)
263            csum = self.checksum(str4)
264            str4 += "%02x\r\n" % csum
265            # print str4
266           
267            buf = str1 + str2 + str3 + str4
268            # stderr.write(buf)
269            # Write NMEA sentences to pipe for IPC to adsbclient.pl (Flarmradar)
270            self.my_fifo.write(buf)
271               
272           
273            # self.d.sendto(buf, (self.dst_host, self.dst_port))
274 
275    except KeyboardInterrupt:
276      # Avoid garble on ^C
277      print ""
278
279if __name__ == '__main__':
280
281  opts = { }
282
283  argc = len(argv)
284  if argc > 1:
285    opts["src_host"]    = argv[1]
286 
287  if argc > 2:
288    opts["src_port"]    = int(argv[2])
289
290  if argc > 3:
291    opts["dst_host"]    = argv[3]
292 
293  if argc > 4:
294    opts["dst_port"]    = int(argv[4])
295 
296  session = adsb2udp(**opts)
297  session.process()
298
299# driver.py ends here
Note: See TracBrowser for help on using the repository browser.