From 8c0eb263b90eb09863718eb580881090d5b8d3b8 Mon Sep 17 00:00:00 2001 From: Joerg Schultze-Lutter Date: Sat, 23 Mar 2024 21:58:30 +0100 Subject: [PATCH] new TAF keyword --- docs/COMMANDS/ACTION_KEYWORDS.md | 22 +++--- docs/EXAMPLES.md | 73 ++++++++++--------- docs/USAGE.md | 2 + src/airport_data_modules.py | 50 ++++++++++--- src/input_parser.py | 116 +++++++++++++++++++++---------- src/mpad_config.py | 2 +- src/output_generator.py | 20 ++++-- src/parser_test.py | 10 ++- 8 files changed, 194 insertions(+), 101 deletions(-) diff --git a/docs/COMMANDS/ACTION_KEYWORDS.md b/docs/COMMANDS/ACTION_KEYWORDS.md index 5733418..f69d393 100644 --- a/docs/COMMANDS/ACTION_KEYWORDS.md +++ b/docs/COMMANDS/ACTION_KEYWORDS.md @@ -113,11 +113,11 @@ You have three options: - specify a specific ICAO code - specify a specific IATA code -- specify the METAR keyword, which instructs the program to look for the airport that is close to your position. That 'nearest' airport position can either be based on the user's own call sign or alternatively on a different user's call sign. +- specify the ```metar``` or the ```taf``` keyword, which instructs the program to look for the airport that is close to your position. That 'nearest' airport position can either be based on the user's own call sign or alternatively on a different user's call sign. If the given airport or the nearest one has been found but does __not__ support METAR data, the program will try to provide you with a standard WX report for the airport's coordinates instead. If the airport is capable of providing METAR data but the METAR report cannot be retrieved, an error message is returned to the user. -Action Keyword can be combined with [date](DATE_KEYWORDS.md) / [daytime](DAYTIME_KEYWORDS.md) keyword parameters: __NO__. If WX data is returned, 'today'/'full' settings will be applied. +Action Keyword can be combined with [date](DATE_KEYWORDS.md) / [daytime](DAYTIME_KEYWORDS.md) keyword parameters: __YES__. If the user specifies the ```metar``` or the ```taf``` keyword in combination with the ```full``` keyword, the returned message will contain both METAR and TAF information. Otherwise, only the data related to the given keyword is returned (e.g. METAR data for ```metar```, TAF data for ```taf```). If WX data is returned, 'today'/'full' settings will be applied. ### ICAO METAR / wx inquiries @@ -140,7 +140,7 @@ Get a METAR/TAF report for a specific ICAO code. If the ICAO code is valid but t ```EDDF 171150Z 02008KT 340V050 5000 -SHSNRA FEW004 SCT011CB BKN019``` -```03/01 Q1023 NOSIG ### TAF EDDF 171100Z 1712/1818 02008KT 9999``` +```03/01 Q1023 NOSIG ## TAF EDDF 171100Z 1712/1818 02008KT 9999``` ```BKN030 TEMPO 1712/1716 SHRAGS BKN020TCU BECMG 1717/1720 FEW030``` @@ -167,14 +167,18 @@ Get a METAR/TAF report for a specific IATA code by retrieving its associated ICA Specifying an IATA code without keyword may or may not be successful as as there is a log of ambiguity. -### METAR keyword +### METAR / TAF keywords -Get a METAR/TAF report for the nearest airport in relation to the user's own call sign or a different call sign +Get a METAR / TAF report for the nearest airport in relation to the user's own call sign or a different call sign #### Formats -- ```metar [-ssid]``` - metar report that is closest to the call sign's position -- ```metar``` - metar/taf report that is closest to the user's own position +- ```metar [-ssid]``` - METAR report that is closest to the call sign's position +- ```taf [-ssid]``` - TAF report that is closest to the call sign's position +- ```metar [-ssid] full``` or ```taf [-ssid] full``` - METAR & TAF report that is closest to the call sign's position +- ```metar``` - METAR report that is closest to the user's own position +- ```taf``` - TAF report that is closest to the user's own position +- ```metar full``` or ```taf full``` - METAR & TAF report that is closest to the user's own position If no call sign is specified, then the user's own call sign (the one that he has send us the message with) is used @@ -182,9 +186,9 @@ If no call sign is specified, then the user's own call sign (the one that he has ```metar ko4jvr-9``` -```metar lb7ji``` +```taf lb7ji``` -```metar ``` +```metar full``` Based on the user's lat/lon, the program will then try to find the nearest airport for you. If that airport supports METAR data, the program is going to return METAR data to the user. Otherwise, it will try to pull a standard wx report for the airport's coordinates. If the airport is capable of providing METAR data but the METAR report cannot be retrieved, an error message is returned to the user. diff --git a/docs/EXAMPLES.md b/docs/EXAMPLES.md index f82e4c5..9578b98 100644 --- a/docs/EXAMPLES.md +++ b/docs/EXAMPLES.md @@ -10,32 +10,32 @@ External service dependencies: - [Openstreetmap](www.openstreetmap.org) for coordinate transformation (e.g. City/country or zipcode to lat/lon) - [aprs.fi](www.aprs.fi) for APRS call sign coordinates -| What do we want | Command string User > MPAD | Response example MPAD > User | -| --------------- | -------------------------- | ---------------------------- | +| What do we want | Command string User > MPAD | Response example MPAD > User | +| --------------- | -------------------------- |------------------------------------------------------------------------| | localised Wx report for the city of Holzminden, Germany | ```Holzminden;de tomorrow lang de``` | ```16-Jan-21 Holzminden;DE Bedeckt morn:-3c day:-1c eve:-2c nite:-2c``` | -| | | ```sunrise/set 08:21/16:42UTC clouds:89% uvi:0.5 hPa:1026 hum:92%``` | -| | | ```dewpt:-5c wndspd:2m/s wnddeg:252``` | +| | | ```sunrise/set 08:21/16:42UTC clouds:89% uvi:0.5 hPa:1026 hum:92%``` | +| | | ```dewpt:-5c wndspd:2m/s wnddeg:252``` | | WX report for monday for the user's own current position | ```monday``` | ```18-Jan-21 Hoexter;DE rain and snow morn:-1c day:1c eve:2c nite:0c``` | | | | ```sunrise/set 08:25/16:48UTC rain:1mm snow:2mm clouds:100% uvi:0.3``` | -| | | ```hPa:1017 hum:98% dewpt:1c wndspd:2m/s wnddeg:223``` | -| WX report for monday for another user's position | ```wa1gov-10 monday``` | ```20-Jan-21 Taunton,MA,02718;US overcast clouds morn:-1c day:1c``` | -| | | ```eve:0c nite:-4c sunrise/set 13:06/22:43UTC clouds:100% uvi:0.9```| -| | |```hPa:1010 hum:84% dewpt:-4c wndspd:1m/s wnddeg:280```| -| Wx report for zipcode 94043 |```94043``` | ```17-Jan-21 Mountain View,94043;US clear sky morn:13c day:22c eve:16c```| -| U.S. country code is added implicitly for a 5-digit zip|```zip 94043``` returns same results| ```nite:14c sunrise/set 16:20/02:16UTC clouds:1% uvi:2.6 hPa:1019```| -| (see keyword specification) | |```hum:27% dewpt:2c wndspd:2m/s wnddeg:353```| -| WX report for zipcode 37603 in Germany |```zip 37603;de``` | ```19-Jan-21 Holzminden,37603;DE moderate rain morn:3c day:4c eve:8c```| -| | same as ```37603;de``` |```nite:5c sunrise/set 08:18/16:46UTC rain:13mm clouds:100% uvi:0.3```| -| | |```hPa:1006 hum:90% dewpt:3c wndspd:7m/s wnddeg:217```| -| WX report for Grid JO41du |```grid jo41du``` | ```17-Jan-21 jo41du rain and snow morn:-1c day:0c eve:1c nite:2c```| -| |```mh jo41du``` or ```jo41du``` returns same results | ```sunrise/set 08:25/16:48UTC rain:1mm snow:2mm clouds:100% uvi:0.3```| -| | |```hPa:1018 hum:97% dewpt:-1c wndspd:2m/s wnddeg:153```| -| Wx report for numeric latitude and longitude|```50.1211/8.7938``` | ```19-Jan-21 Offenbach am Main,63075;DE moderate rain morn:2c day:3c```| -| | |```eve:5c nite:3c sunrise/set 08:14/16:56UTC rain:5mm clouds:100%```| -| | |```uvi:0.1 hPa:1014 hum:79% dewpt:0c wndspd:8m/s wnddeg:217```| -| WX report in 47h for zipcode 37627 in Germany |```zip 37603;de 47h``` | ```15-Feb-21 in 47h Stadtoldendorf,37627;DE overcast clouds temp:-2c```| -| | |```clouds:100% uvi:0.5 hPa:1032 hum:87% dewpt:-8c wndspd:5m/s```| -| | |```wnddeg:166 vis:10000m```| +| | | ```hPa:1017 hum:98% dewpt:1c wndspd:2m/s wnddeg:223``` | +| WX report for monday for another user's position | ```wa1gov-10 monday``` | ```20-Jan-21 Taunton,MA,02718;US overcast clouds morn:-1c day:1c``` | +| | | ```eve:0c nite:-4c sunrise/set 13:06/22:43UTC clouds:100% uvi:0.9``` | +| | | ```hPa:1010 hum:84% dewpt:-4c wndspd:1m/s wnddeg:280``` | +| Wx report for zipcode 94043 |```94043``` | ```17-Jan-21 Mountain View,94043;US clear sky morn:13c day:22c eve:16c``` | +| U.S. country code is added implicitly for a 5-digit zip|```zip 94043``` returns same results| ```nite:14c sunrise/set 16:20/02:16UTC clouds:1% uvi:2.6 hPa:1019``` | +| (see keyword specification) | | ```hum:27% dewpt:2c wndspd:2m/s wnddeg:353``` | +| WX report for zipcode 37603 in Germany |```zip 37603;de``` | ```19-Jan-21 Holzminden,37603;DE moderate rain morn:3c day:4c eve:8c``` | +| | same as ```37603;de``` | ```nite:5c sunrise/set 08:18/16:46UTC rain:13mm clouds:100% uvi:0.3``` | +| | | ```hPa:1006 hum:90% dewpt:3c wndspd:7m/s wnddeg:217``` | +| WX report for Grid JO41du |```grid jo41du``` | ```17-Jan-21 jo41du rain and snow morn:-1c day:0c eve:1c nite:2c``` | +| |```mh jo41du``` or ```jo41du``` returns same results | ```sunrise/set 08:25/16:48UTC rain:1mm snow:2mm clouds:100% uvi:0.3``` | +| | | ```hPa:1018 hum:97% dewpt:-1c wndspd:2m/s wnddeg:153``` | +| Wx report for numeric latitude and longitude|```50.1211/8.7938``` | ```19-Jan-21 Offenbach am Main,63075;DE moderate rain morn:2c day:3c``` | +| | | ```eve:5c nite:3c sunrise/set 08:14/16:56UTC rain:5mm clouds:100%``` | +| | | ```uvi:0.1 hPa:1014 hum:79% dewpt:0c wndspd:8m/s wnddeg:217``` | +| WX report in 47h for zipcode 37627 in Germany |```zip 37603;de 47h``` | ```15-Feb-21 in 47h Stadtoldendorf,37627;DE overcast clouds temp:-2c``` | +| | | ```clouds:100% uvi:0.5 hPa:1032 hum:87% dewpt:-8c wndspd:5m/s``` | +| | | ```wnddeg:166 vis:10000m``` | ## Repeater @@ -82,20 +82,25 @@ External service dependencies: - [Aviation Weather](www.aviationweather.gov) for coordinate transformation (airport code to lat/lon) and METAR/TAF data - [aprs.fi](www.aprs.fi) for APRS call sign coordinates -| What do we want | Command string User > MPAD | Response example MPAD > User | -| --------------- | -------------------------- | ---------------------------- | -| METAR / TAF data of a METAR-enabled airport, related to the user's position | ```metar``` | ```EDDF 171150Z 02008KT 340V050 5000 -SHSNRA FEW004 SCT011CB BKN019``` | -| | | ```03/01 Q1023 NOSIG ### TAF EDDF 171100Z 1712/1818 02008KT 9999``` | -| | | ```BKN030 TEMPO 1712/1716 SHRAGS BKN020TCU BECMG 1717/1720 FEW030``` | -| | | ```BECMG 1800/1802 02002KT BECMG 1806/1809 30005KT TEMPO 1811/1818``` | -| | | ```SHRAGS BKN020TCU SCT030``` | -| METAR data of a METAR-enabled airport, related to another user's position | ```metar wa1gov-10``` | similar output to 1st example | -| METAR data for ICAO code EDDF | ```icao eddf``` or ```eddf``` | similar output to 1st example | -| METAR data for IATA code FRA | ```iata fra``` or ```fra``` | similar output to 1st example | +| What do we want | Command string User > MPAD | Response example MPAD > User | +|---------------------------------------------------------------------------------|----------------------------------------|----------------------------------------------------------------------------| +| METAR data of a METAR-enabled airport, related to the user's position | ```metar``` | ```EDDF 171150Z 02008KT 340V050 5000 -SHSNRA FEW004 SCT011CB BKN019``` | +| | | ```03/01 Q1023 NOSIG``` | +| TAF data of a METAR-enabled airport, related to the user's position | ```taf``` | ```TAF EDDF 171100Z 1712/1818 02008KT 9999 BKN030 TEMPO 1712/1716``` | +| | | ```SHRAGS BKN020TCU BECMG 1717/1720 FEW030 BECMG 1800/1802 02002KT ``` | +| | | ```BECMG 1806/1809 30005KT TEMPO 1811/1818 SHRAGS BKN020TCU SCT030``` | +| METAR _and_ TAF data of a METAR-enabled airport, related to the user's position | ```metar full``` __or__ ```taf full``` | ```EDDF 171150Z 02008KT 340V050 5000 -SHSNRA FEW004 SCT011CB BKN019``` | +| | | ```03/01 Q1023 NOSIG ## TAF EDDF 171100Z 1712/1818 02008KT 9999``` | +| | | ```BKN030 TEMPO 1712/1716 SHRAGS BKN020TCU BECMG 1717/1720 FEW030``` | +| | | ```BECMG 1800/1802 02002KT BECMG 1806/1809 30005KT TEMPO 1811/1818``` | +| | | ```SHRAGS BKN020TCU SCT030``` | +| METAR data of a METAR-enabled airport, related to another user's position | ```metar wa1gov-10``` | similar output to 1st example; add ```full``` keyword for METAR & TAF data | +| METAR data for ICAO code EDDF | ```icao eddf``` or ```eddf``` | similar output to 1st example; add ```full``` keyword for METAR & TAF data | +| METAR data for IATA code FRA | ```iata fra``` or ```fra``` | similar output to 1st example; add ```full``` keyword for METAR & TAF data | IATA codes are taken from [https://www.aviationweather.gov/docs/metar/stations.txt](https://www.aviationweather.gov/docs/metar/stations.txt). This file does not contain several international IATA codes. If your IATA code does not work, use an ICAO code. -For better legibility, METAR and TAF data are separated by a ### sequence - see example. +In case you use either the ```metar``` or the ```taf``` keyword in combination with the ```full``` keyword, `For better legibility,```mpad``` will separate METAR and TAF data by a ## sequence - see example. ## CWOP data diff --git a/docs/USAGE.md b/docs/USAGE.md index 3eeea48..e6efb6c 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -4,6 +4,8 @@ - The program's default ```action``` is to retrieve a wx report for the given address/coordinates. This assumption is valid as long as the user has not specified a keyword that tells the program to do something different. This means that you can a single datetime-related keyword like e.g. 'tomorrow' and the program will try to return the wx report for you. +- By default, the ```metar``` and ```taf``` commands will only return the data requested by the user, read: if ```metar``` data was requested, the user will only receive METAR data. You can request both METAR and TAF data at the same time by adding the ```full``` keyword to your inquiry, e.g. ```metar full``` or ```taf full``` will return both METAR and TAF information to the user. + - Default ```date``` is always ```today```. If you omit any date-related information, ```today``` will be the default value. - Default ```daytime``` is always ```full```, meaning that you will get e.g. the wx report for the whole day. See the next paragraph on limitations wrt keywords and time zones. Note that ```full``` can be time- or content-specific. diff --git a/src/airport_data_modules.py b/src/airport_data_modules.py index a2ffd98..aea12d4 100644 --- a/src/airport_data_modules.py +++ b/src/airport_data_modules.py @@ -75,7 +75,7 @@ def read_local_airport_data_file( except: lines = None else: - logger.info(msg=f"Airport data file '{absolute_path_filename}' does not exist") + logger.error(msg=f"Airport data file '{absolute_path_filename}' does not exist") # If the file did contain content, then parse it if lines: @@ -134,7 +134,7 @@ def read_local_airport_data_file( return iata_dict, icao_dict -def get_metar_data(icao_code: str): +def get_metar_data(icao_code: str, keyword: str = None, full_msg: bool = False): """ Get METAR and TAF data for a given ICAO code. May need to be switched to https://api.met.no/weatherapi/ at @@ -145,6 +145,13 @@ def get_metar_data(icao_code: str): icao_code : 'str' 4-character ICAO code of the airport whose METAR data is to be downloaded. + keyword: 'str' + mode that we intend to run; value is either + "metar" or "taf" + full_msg: 'bool' + True = always forces the selected keyword to + return both METAR and TAF data + False = only returns METAR or TAF data Returns ======= @@ -155,12 +162,21 @@ def get_metar_data(icao_code: str): (or "NOTFOUND" if no data was found) """ + assert keyword in ("metar", "taf") + + # prepare the request parameters + # false = request METAR data but no TAF data + + request_mode = "false" + if keyword == "taf" or full_msg == True: + request_mode = "true" + try: resp = requests.get( f"https://www.aviationweather.gov/" f"cgi-bin/data/metar.php/?ids={icao_code}" - f"&hours=0&order=id%2C-obs&sep=true" - f"&taf=true" + f"&hours=0&order=id%2C-obs&sep=false" + f"&taf={request_mode}" ) except requests.exceptions.RequestException as e: logger.error(msg="{e}") @@ -186,13 +202,27 @@ def get_metar_data(icao_code: str): metar_result_array = remainder.splitlines() # start with an empty response response = "" + metar = "" + taf = "" # Iterate through the list. We stop at that line which starts with TAF (TAF report) # Everything else before that line is our METAR report which may consist of 1..n lines - for metar_result_item in metar_result_array: - if metar_result_item.startswith("TAF"): - response = response + " ### " - response = response + metar_result_item + for index, metar_result_item in enumerate(metar_result_array): + if index == 0: + # METAR content is ALWAYS received and consists of a single line + metar = metar_result_item + else: + # TAF content may be optional, starts at index 1 and may consist of 1..n lines + taf = taf + metar_result_item.lstrip() + # end of parser; construct the outgoing message success = True + + # if the user requested both METAR and TAF data, + # concatenate both messages with a separator + if full_msg: + response = f"{metar} ## {taf}" + else: + # assign either the METAR or TAF content as response + response = metar if keyword == "metar" else taf response = response.strip() return success, response @@ -316,7 +346,7 @@ def update_local_airport_stations_file( try: r = requests.get(file_url) except: - logger.info(msg=f"Cannot download airport data from '{file_url}'") + logger.debug(msg=f"Cannot download airport data from '{file_url}'") r = None if r: if r.status_code == 200: @@ -326,7 +356,7 @@ def update_local_airport_stations_file( f.close() success = True except: - logger.info( + logger.debug( msg=f"Cannot update airport data to local file '{absolute_path_filename}'" ) return success diff --git a/src/input_parser.py b/src/input_parser.py index da17e9c..0207730 100644 --- a/src/input_parser.py +++ b/src/input_parser.py @@ -220,6 +220,7 @@ def parse_input_message(aprs_message: str, users_callsign: str, aprsdotfi_api_ke # whereis (location information for the user's position) # riseset (Sunrise/Sunset and moonrise/moonset info) # metar (nearest METAR data for the user's position) + # taf (nearest TAF date for the user's position) # CWOP (nearest CWOP data for user's position) # if not found_my_duty_roster and not err: @@ -325,7 +326,11 @@ def parse_input_message(aprs_message: str, users_callsign: str, aprsdotfi_api_ke # Search for repeater-mode-band if not found_my_duty_roster and not err: - (found_my_keyword, kw_err, parser_rd_repeater,) = parse_what_keyword_repeater( + ( + found_my_keyword, + kw_err, + parser_rd_repeater, + ) = parse_what_keyword_repeater( aprs_message=aprs_message, users_callsign=users_callsign, aprsdotfi_api_key=aprsdotfi_api_key, @@ -441,7 +446,11 @@ def parse_input_message(aprs_message: str, users_callsign: str, aprsdotfi_api_ke # parsing and needs to be placed relatively to the end of the input parser # in order to avoid misinterpretations wrt the message content. if not found_my_duty_roster and not err: - (found_my_keyword, kw_err, parser_rd_metar,) = parse_what_keyword_metar( + ( + found_my_keyword, + kw_err, + parser_rd_metar, + ) = parse_what_keyword_metar( aprs_message=aprs_message, users_callsign=users_callsign, aprsdotfi_api_key=aprsdotfi_api_key, @@ -470,7 +479,11 @@ def parse_input_message(aprs_message: str, users_callsign: str, aprsdotfi_api_ke # chance of misinterpreting the user's message if not found_my_duty_roster and not err: - (found_my_keyword, kw_err, parser_rd_wx,) = parse_what_keyword_wx( + ( + found_my_keyword, + kw_err, + parser_rd_wx, + ) = parse_what_keyword_wx( aprs_message=aprs_message, users_callsign=users_callsign, language=language ) # did we find something? Then overwrite the existing variables with the retrieved content @@ -507,25 +520,6 @@ def parse_input_message(aprs_message: str, users_callsign: str, aprsdotfi_api_ke # for. We only enter the WHEN parser routines in case the # previous parser did nor encounter any errors. # - # Parse the "when" information if we don't have an error - # and if we haven't retrieved the command data in a previous run - # if not found_when and not err: - # _msg, found_when, when, date_offset, hour_offset = parse_when(aprs_message) - # # if we've found something, then replace the original APRS message with - # # what is left of it (minus the parts that were removed by the parse_when - # # parser code - # if found_when: - # aprs_message = _msg - - # Parse the "when_daytime" information if we don't have an error - # and if we haven't retrieved the command data in a previous run - # if not found_when_daytime and not err: - # _msg, found_when_daytime, when_daytime = parse_when_daytime(aprs_message) - # # if we've found something, then replace the original APRS message with - # # what is left of it (minus the parts that were removed by the parse_when - # # parser code - # if found_when_daytime: - # aprs_message = _msg # # We have reached the very end of the parser @@ -564,8 +558,17 @@ def parse_input_message(aprs_message: str, users_callsign: str, aprsdotfi_api_ke date_offset = 0 # apply default to 'when_daytime' if still not populated + # for special keywords such as the "metar" keyword, we + # (ab)use the when_daytime field for other purposes, thus + # allowing us to control the output in a proper way UNLESS + # the user has explicitly specified the "full" command if not found_when_daytime and not err: - when_daytime = "full" + if what in ("metar", "taf"): + # populate with some default value so that we + # don't run into trouble later on + when_daytime = "day" + else: + when_daytime = "full" found_when_daytime = True # apply default to 'what' if still not populated @@ -1142,7 +1145,7 @@ def parse_what_keyword_metar( ): """ Keyword parser for the IATA/ICAO/METAR keywords (resulting in - a request for METAR data for a specific airport) + a request for METAR or TAF data for a specific airport) Parameters ========== @@ -1360,6 +1363,52 @@ def parse_what_keyword_metar( icao = None human_readable_message = f"Wx for '{icao}'" + # if the user has specified the 'taf' keyword, then + # try to determine the nearest airport in relation to + # the user's own call sign position + if not found_my_keyword and not kw_err: + regex_string = r"\b(taf)\b" + matches = re.findall( + pattern=regex_string, string=aprs_message, flags=re.IGNORECASE + ) + if matches: + ( + success, + latitude, + longitude, + altitude, + lasttime, + comment, + message_callsign, + ) = get_position_on_aprsfi( + aprsfi_callsign=users_callsign, aprsdotfi_api_key=aprsdotfi_api_key + ) + if success: + icao = get_nearest_icao(latitude=latitude, longitude=longitude) + if icao: + ( + success, + latitude, + longitude, + metar_capable, + icao, + ) = validate_icao(icao) + if success: + what = "taf" + human_readable_message = f"TAF for '{icao}'" + found_my_keyword = True + aprs_message = re.sub( + pattern=regex_string, + repl="", + string=aprs_message, + flags=re.IGNORECASE, + ).strip() + # If we did find the airport but it is not METAR-capable, + # then supply a wx report instead + if not metar_capable: + what = "wx" + icao = None + human_readable_message = f"Wx for '{icao}'" parser_rd_metar = { "what": what, "message_callsign": users_callsign, @@ -2153,6 +2202,7 @@ def parse_what_keyword_callsign_multi( whereis (location information for the user's position) riseset (Sunrise/Sunset and moonrise/moonset info) metar (nearest METAR data for the user's position) + taf (nearest taf data for the user's position) CWOP (nearest CWOP data for user's position) Parameters @@ -2194,7 +2244,7 @@ def parse_what_keyword_callsign_multi( # sequence and -if found- use the user's call sign # # Check - full call sign with SSID - regex_string = r"\b(wx|forecast|whereis|riseset|cwop|metar|sonde)\s*([a-zA-Z0-9]{1,3}[0-9][a-zA-Z0-9]{0,3}-[a-zA-Z0-9]{1,2})\b" + regex_string = r"\b(wx|forecast|whereis|riseset|cwop|metar|taf|sonde)\s*([a-zA-Z0-9]{1,3}[0-9][a-zA-Z0-9]{0,3}-[a-zA-Z0-9]{1,2})\b" matches = re.search(pattern=regex_string, string=aprs_message, flags=re.IGNORECASE) if matches: what = matches[1].lower() @@ -2205,7 +2255,7 @@ def parse_what_keyword_callsign_multi( found_my_keyword = True if not found_my_keyword: # Check - call sign without SSID - regex_string = r"\b(wx|forecast|whereis|riseset|cwop|metar|sonde)\s*([a-zA-Z0-9]{1,3}[0-9][a-zA-Z0-9]{0,3})\b" + regex_string = r"\b(wx|forecast|whereis|riseset|cwop|metar|taf|sonde)\s*([a-zA-Z0-9]{1,3}[0-9][a-zA-Z0-9]{0,3})\b" matches = re.search( pattern=regex_string, string=aprs_message, flags=re.IGNORECASE ) @@ -2218,7 +2268,7 @@ def parse_what_keyword_callsign_multi( ).strip() if not found_my_keyword: # Check - call sign whose pattern deviates from the standard call sign pattern (e.g. bot, CWOP station etc) - regex_string = r"\b(wx|forecast|whereis|riseset|cwop|metar|sonde)\s*(\w+)\b" + regex_string = r"\b(wx|forecast|whereis|riseset|cwop|metar|taf|sonde)\s*(\w+)\b" matches = re.search( pattern=regex_string, string=aprs_message, flags=re.IGNORECASE ) @@ -2235,7 +2285,7 @@ def parse_what_keyword_callsign_multi( # Hint: normally excludes the 'sonde' keyword as it requires a separate ID # But maybe the probe itself is asking for pos data so let's keep it in # Future processing of probe data will fail anyway if it's no radiosonde callsign - regex_string = r"\b(wx|forecast|whereis|riseset|cwop|metar|sonde)\b" + regex_string = r"\b(wx|forecast|whereis|riseset|cwop|metar|taf|sonde)\b" matches = re.search( pattern=regex_string, string=aprs_message, flags=re.IGNORECASE ) @@ -2323,7 +2373,7 @@ def parse_what_keyword_callsign_multi( elif what == "cwop": human_readable_message = f"CWOP for {message_callsign}" what = "cwop_by_latlon" - elif what == "metar": + elif what in ("metar", "taf"): icao = get_nearest_icao(latitude=latitude, longitude=longitude) if icao: ( @@ -2335,7 +2385,7 @@ def parse_what_keyword_callsign_multi( ) = validate_icao(icao_code=icao) if success: found_my_keyword = True - human_readable_message = f"METAR for '{icao}'" + human_readable_message = f"{what.upper()} for '{icao}'" # If we did find the airport but it is not METAR-capable, # then supply a wx report instead if not metar_capable: @@ -2951,8 +3001,4 @@ def parse_what_keyword_email_position_report( smtpimap_email_address, smtpimap_email_password, ) = read_program_config() - logger.info( - pformat( - parse_input_message("los angeles unicode", "df1jsl-1", aprsdotfi_api_key) - ) - ) + logger.info(pformat(parse_input_message("taf eddf", "df1jsl-1", aprsdotfi_api_key))) diff --git a/src/mpad_config.py b/src/mpad_config.py index 649fe87..c7a17c4 100644 --- a/src/mpad_config.py +++ b/src/mpad_config.py @@ -22,7 +22,7 @@ # # Program version # -mpad_version: str = "0.41" +mpad_version: str = "0.42" # ########################### # Constants, do not change# diff --git a/src/output_generator.py b/src/output_generator.py index 517306b..d29b945 100644 --- a/src/output_generator.py +++ b/src/output_generator.py @@ -102,7 +102,7 @@ def generate_output_message(response_parameters: dict): success, output_list = generate_output_message_wx( response_parameters=response_parameters, ) - elif what == "metar": + elif what in ("metar", "taf"): logger.info("Running output worker generate_output_message_metar()") success, output_list = generate_output_message_metar( response_parameters=response_parameters @@ -284,7 +284,7 @@ def generate_output_message_wx(response_parameters: dict): def generate_output_message_metar(response_parameters: dict): """ - Generate a metar report for a specific airport (ICAO code) + Generate a METAR and/or TAF report for a specific airport (ICAO code) Parameters ========== @@ -310,7 +310,18 @@ def generate_output_message_metar(response_parameters: dict): # shorten the user message # icao_code = response_parameters["icao"] - success, metar_response = get_metar_data(icao_code=icao_code) + + # now get the action command which was requested by the user + # value can only be "metar" or "taf" + _what = response_parameters["what"] + + # and get info on whether the user wants both ME + when_daytime = response_parameters["when_daytime"] + _full = True if when_daytime == "full" else False + + success, metar_response = get_metar_data( + icao_code=icao_code, keyword=_what, full_msg=_full + ) if success: output_list = make_pretty_aprs_messages( message_to_add=metar_response, @@ -898,7 +909,6 @@ def generate_output_message_radiosonde(response_parameters: dict): ) if landing_latitude != users_latitude and landing_longitude != users_longitude: - # We have different identities and switch from "whereami" mode # to the "whereis" mode where we will also calculate the distance, # heading and direction between these two positions @@ -1043,7 +1053,6 @@ def generate_output_message_whereis(response_parameters: dict): # calculate distance, heading and bearing if message call sign position # differs from our own call sign's position if latitude != users_latitude and longitude != users_longitude: - # We have different identities and switch from "whereami" mode # to the "whereis" mode where we will also calculate the distance, # heading and direction between these two positions @@ -1170,7 +1179,6 @@ def generate_output_message_repeater(response_parameters: dict): ) # success = we have at least one dict entry in our list if success: - number_of_actual_results = len(nearest_repeater_list) entry = 0 diff --git a/src/parser_test.py b/src/parser_test.py index ae38615..258b887 100644 --- a/src/parser_test.py +++ b/src/parser_test.py @@ -53,9 +53,7 @@ def testcall(message_text: str, from_callsign: str): logger.info(msg="Response:") logger.info(msg=pformat(generate_output_message(response_parameters))) else: - human_readable_message = response_parameters[ - "human_readable_message" - ] + human_readable_message = response_parameters["human_readable_message"] # Dump the HRM to the user if we have one if human_readable_message: output_message = make_pretty_aprs_messages( @@ -66,11 +64,11 @@ def testcall(message_text: str, from_callsign: str): else: output_message = [ "Sorry, did not understand your request. Have a look at my command", - "syntax, see https://github.com/joergschultzelutter/mpad" + "syntax, see https://github.com/joergschultzelutter/mpad", ] logger.info(output_message) - #logger.info(msg=pformat(response_parameters)) + # logger.info(msg=pformat(response_parameters)) if __name__ == "__main__": - testcall(message_text="fhadsjfhjdsahf", from_callsign="ua3mlr-9") + testcall(message_text="taf df1jsl-1", from_callsign="df1jsl-1")