25
25
from collections import deque
26
26
from confluent_kafka import Producer
27
27
28
- if sys .gettrace () is not None :
29
- logging .basicConfig (level = logging .DEBUG , format = '%(asctime)s - %(levelname)s - %(message)s' )
30
- else :
31
- logging .basicConfig (level = logging .INFO , format = '%(asctime)s - %(levelname)s - %(message)s' )
32
-
33
28
logger = logging .getLogger (__name__ )
29
+ logger .handlers = []
30
+ logger .setLevel (logging .DEBUG if sys .gettrace () else logging .INFO )
31
+
32
+ stream_handler = logging .StreamHandler (sys .stdout )
33
+ stream_handler .setFormatter (logging .Formatter ('%(asctime)s - %(levelname)s - %(message)s' ))
34
+ logger .addHandler (stream_handler )
35
+ logger .propagate = False
34
36
35
37
36
38
class ADSBClient (TcpClient ):
@@ -62,75 +64,78 @@ def handle_messages(self, messages):
62
64
return
63
65
msgs = []
64
66
for msg , ts in messages :
65
- raw_msg = bytes .fromhex (msg )
66
- if len (raw_msg ) < 7 :
67
- continue
68
- ts_ms = int (ts * 1000 )
69
- df = pms .df (msg )
70
- icao = pms .icao (msg )
71
- dbfs_rssi = None
72
- raw_rssi = raw_msg [6 ]
73
- if raw_rssi > 0 :
74
- rssi_ratio = raw_rssi / 255
75
- signal_level = rssi_ratio ** 2
76
- dbfs_rssi = round (10 * math .log10 (signal_level ), 2 )
77
-
78
- record = ModeS_ADSB_Record (
79
- ts = ts_ms , icao = icao , df = df , rssi = dbfs_rssi , tc = None , bcode = None , alt = None , cs = None , sq = None , lat = None , lon = None ,
80
- spd = None , ang = None , vr = None , spd_type = None , dir_src = None , vr_src = None , ws = None , wd = None , at = None , ap = None , hm = None ,
81
- roll = None , trak = None , gs = None , tas = None , hd = None , ias = None , m = None , vrb = None , vri = None , emst = None , tgt = None , opst = None
82
- )
83
- if df in (17 , 18 ):
84
- if pms .crc (msg ) != 0 :
67
+ try :
68
+ raw_msg = bytes .fromhex (msg )
69
+ if len (raw_msg ) < 7 :
70
+ logger .warning ("Invalid message (too short): %s" , msg )
85
71
continue
86
- tc = pms .typecode (msg )
87
- record .tc = tc
88
- if 1 <= tc <= 4 :
89
- record .cs = pms .adsb .callsign (msg )
90
- elif 5 <= tc <= 8 :
91
- lat , lon = pms .adsb .surface_position_with_ref (msg , self .ref_lat , self .ref_lon )
92
- record .lat , record .lon = lat , lon
93
- elif 9 <= tc <= 18 :
94
- record .alt = pms .adsb .altitude (msg )
95
- lat , lon = pms .adsb .airborne_position_with_ref (msg , self .ref_lat , self .ref_lon )
96
- record .lat , record .lon = lat , lon
97
- elif tc == 19 :
98
- speed , angle , vr , spd_type , * extras = pms .adsb .velocity (msg )
99
- record .spd , record .ang , record .vr = speed , angle , vr
100
- record .spd_type = spd_type
101
- if len (extras ) > 0 :
102
- record .dir_src = extras [0 ]
103
- if len (extras ) > 1 :
104
- record .vr_src = extras [1 ]
105
- elif 20 <= tc <= 22 :
106
- record .alt = pms .adsb .altitude (msg )
107
- lat , lon = pms .adsb .airborne_position_with_ref (msg , self .ref_lat , self .ref_lon )
108
- record .lat , record .lon = lat , lon
109
- elif df in (20 , 21 ):
110
- bds = pms .bds .infer (msg , mrar = True )
111
- record .bcode = bds if bds else None
112
- if df == 20 :
113
- record .alt = pms .common .altcode (msg )
114
- if df == 21 :
115
- record .sq = str (pms .common .idcode (msg ))
116
- if bds == "BDS44" :
117
- ws , wd = pms .commb .wind44 (msg )
118
- record .ws , record .wd = ws , wd
119
- record .at = pms .commb .temp44 (msg )
120
- record .ap = pms .commb .p44 (msg )
121
- record .hm = pms .commb .hum44 (msg )
122
- elif bds == "BDS50" :
123
- record .roll = pms .commb .roll50 (msg )
124
- record .trak = pms .commb .trk50 (msg )
125
- record .gs = pms .commb .gs50 (msg )
126
- record .tas = pms .commb .tas50 (msg )
127
- elif bds == "BDS60" :
128
- record .hd = pms .commb .hdg60 (msg )
129
- record .ias = pms .commb .ias60 (msg )
130
- record .m = pms .commb .mach60 (msg )
131
- record .vrb = pms .commb .vr60baro (msg )
132
- record .vri = pms .commb .vr60ins (msg )
133
- msgs .append (record )
72
+
73
+ ts_ms = int (ts * 1000 )
74
+ df = pms .df (msg )
75
+ icao = pms .icao (msg )
76
+ dbfs_rssi = None
77
+ raw_rssi = raw_msg [6 ]
78
+ if raw_rssi > 0 :
79
+ rssi_ratio = raw_rssi / 255
80
+ signal_level = rssi_ratio ** 2
81
+ dbfs_rssi = round (10 * math .log10 (signal_level ), 2 )
82
+
83
+ record = ModeS_ADSB_Record (
84
+ ts = ts_ms , icao = icao , df = df , rssi = dbfs_rssi , tc = None , bcode = None , alt = None , cs = None , sq = None , lat = None , lon = None ,
85
+ spd = None , ang = None , vr = None , spd_type = None , dir_src = None , vr_src = None , ws = None , wd = None , at = None , ap = None , hm = None ,
86
+ roll = None , trak = None , gs = None , tas = None , hd = None , ias = None , m = None , vrb = None , vri = None , emst = None , tgt = None , opst = None
87
+ )
88
+ if df in (17 , 18 ):
89
+ tc = pms .typecode (msg )
90
+ record .tc = tc
91
+ if 1 <= tc <= 4 :
92
+ record .cs = pms .adsb .callsign (msg )
93
+ elif 5 <= tc <= 8 :
94
+ lat , lon = pms .adsb .surface_position_with_ref (msg , self .ref_lat , self .ref_lon )
95
+ record .lat , record .lon = lat , lon
96
+ elif 9 <= tc <= 18 :
97
+ record .alt = pms .adsb .altitude (msg )
98
+ lat , lon = pms .adsb .airborne_position_with_ref (msg , self .ref_lat , self .ref_lon )
99
+ record .lat , record .lon = lat , lon
100
+ elif tc == 19 :
101
+ speed , angle , vr , spd_type , * extras = pms .adsb .velocity (msg )
102
+ record .spd , record .ang , record .vr = speed , angle , vr
103
+ record .spd_type = spd_type
104
+ if len (extras ) > 0 :
105
+ record .dir_src = extras [0 ]
106
+ if len (extras ) > 1 :
107
+ record .vr_src = extras [1 ]
108
+ elif 20 <= tc <= 22 :
109
+ record .alt = pms .adsb .altitude (msg )
110
+ lat , lon = pms .adsb .airborne_position_with_ref (msg , self .ref_lat , self .ref_lon )
111
+ record .lat , record .lon = lat , lon
112
+ elif df in (20 , 21 ):
113
+ bds = pms .bds .infer (msg , mrar = True )
114
+ record .bcode = bds if bds else None
115
+ if df == 20 :
116
+ record .alt = pms .common .altcode (msg )
117
+ if df == 21 :
118
+ record .sq = str (pms .common .idcode (msg ))
119
+ if bds == "BDS44" :
120
+ ws , wd = pms .commb .wind44 (msg )
121
+ record .ws , record .wd = ws , wd
122
+ record .at = pms .commb .temp44 (msg )
123
+ record .ap = pms .commb .p44 (msg )
124
+ record .hm = pms .commb .hum44 (msg )
125
+ elif bds == "BDS50" :
126
+ record .roll = pms .commb .roll50 (msg )
127
+ record .trak = pms .commb .trk50 (msg )
128
+ record .gs = pms .commb .gs50 (msg )
129
+ record .tas = pms .commb .tas50 (msg )
130
+ elif bds == "BDS60" :
131
+ record .hd = pms .commb .hdg60 (msg )
132
+ record .ias = pms .commb .ias60 (msg )
133
+ record .m = pms .commb .mach60 (msg )
134
+ record .vrb = pms .commb .vr60baro (msg )
135
+ record .vri = pms .commb .vr60ins (msg )
136
+ msgs .append (record )
137
+ except Exception as e :
138
+ logger .error ("Invalid message: %s, error: %s" , msg , e )
134
139
135
140
if len (msgs ) > 0 :
136
141
bundle = Messages (messages = msgs )
@@ -164,30 +169,30 @@ async def queue_consumer(self, stop_event: threading.Event):
164
169
flush_producer = False
165
170
)
166
171
if (datetime .now () - last_flush ) > timedelta (seconds = 1 ) or self .records_since_last_flush >= 1000 :
167
- if last_info_log < datetime .now () - timedelta (minutes = 5 ):
168
- logging .info ("Messages %d, records %d, queue length is %d" , messages_since_last_log , records_since_last_log , len (self .task_queue ))
172
+ if last_info_log < datetime .now () - timedelta (seconds = 5 * 60 ):
173
+ logger .info ("Messages %d, records %d, queue length is %d" , messages_since_last_log , records_since_last_log , len (self .task_queue ))
169
174
last_info_log = datetime .now ()
170
175
messages_since_last_log = 0
171
176
records_since_last_log = 0
172
177
else :
173
- logging .debug ("Flushing producer, messages %d, records %d, queue length is %d" , self .messages_since_last_flush , self .records_since_last_flush , len (self .task_queue ))
178
+ logger .debug ("Flushing producer, messages %d, records %d, queue length is %d" , self .messages_since_last_flush , self .records_since_last_flush , len (self .task_queue ))
174
179
self .producer .producer .flush ()
175
180
self .messages_since_last_flush = 0
176
181
self .records_since_last_flush = 0
177
182
last_flush = datetime .now ()
178
183
except asyncio .CancelledError :
179
- logging .info ("Queue consumer task cancelled" )
184
+ logger .info ("Queue consumer task cancelled" )
180
185
return
181
186
except Exception as e :
182
- logging .error ("Error sending messages: %s" , e )
187
+ logger .error ("Error sending messages: %s" , e )
183
188
else :
184
189
await asyncio .sleep (0.05 )
185
190
except asyncio .CancelledError :
186
- logging .info ("Queue consumer task cancelled" )
191
+ logger .info ("Queue consumer task cancelled" )
187
192
return
188
193
except Exception as e :
189
- logging .error ("Queue consumer task error: %s" , e )
190
- raise e
194
+ logger .error ("Queue consumer task error: %s" , e )
195
+ raise e
191
196
192
197
193
198
def parse_connection_string (connection_string : str ) -> Dict [str , str ]:
@@ -252,19 +257,15 @@ async def run():
252
257
if args .subcommand == 'feed' :
253
258
# Host/port/pos checks
254
259
if not args .host :
255
- logging .error ("Missing required parameter: host" )
256
260
print ("Error: Dump1090 host is required (env: DUMP1090_HOST or --host)" )
257
261
return
258
262
if not args .port :
259
- logging .error ("Missing required parameter: port" )
260
263
print ("Error: Dump1090 port is required (env: DUMP1090_PORT or --port)" )
261
264
return
262
265
if args .ref_lat is None :
263
- logging .error ("Missing required parameter: ref_lat" )
264
266
print ("Error: Antenna latitude is required (env: REF_LAT or --ref-lat)" )
265
267
return
266
268
if args .ref_lon is None :
267
- logging .error ("Missing required parameter: ref_lon" )
268
269
print ("Error: Antenna longitude is required (env: REF_LON or --ref-lon)" )
269
270
return
270
271
@@ -282,7 +283,6 @@ async def run():
282
283
sasl_username = config_params .get ('sasl.username' )
283
284
sasl_password = config_params .get ('sasl.password' )
284
285
except ValueError as e :
285
- logging .error ("Invalid connection string: %s" , e )
286
286
print ("Error: Invalid connection string format." )
287
287
return
288
288
else :
@@ -292,15 +292,12 @@ async def run():
292
292
sasl_password = args .sasl_password
293
293
294
294
if not kafka_bootstrap_servers :
295
- logging .error ("Missing required parameter: kafka_bootstrap_servers" )
296
295
print ("Error: No Kafka bootstrap servers found." )
297
296
return
298
297
if not kafka_topic :
299
- logging .error ("Missing required parameter: kafka_topic" )
300
298
print ("Error: No Kafka topic found." )
301
299
return
302
300
if not sasl_username or not sasl_password :
303
- logging .error ("Missing required SASL credentials" )
304
301
print ("Error: SASL username and password are required." )
305
302
return
306
303
@@ -315,7 +312,6 @@ async def run():
315
312
}
316
313
kafka_producer = Producer (kafka_config )
317
314
except Exception as producer_err :
318
- logging .error ("Failed to create Kafka producer: %s" , producer_err )
319
315
print ("Error: Could not create Kafka producer." )
320
316
return
321
317
@@ -347,9 +343,7 @@ def signal_handler(signum, frame):
347
343
348
344
except KeyboardInterrupt :
349
345
print ("Interrupted" )
350
- logging .info ("Application interrupted by user." )
351
346
except Exception as e :
352
- logging .error ("Unhandled startup error: %s" , e )
353
347
print (f"Error: { e } " )
354
348
355
349
def main ():
0 commit comments