From 1ea69613938348e1cc7d87aacf9797349da6086e Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Thu, 12 Dec 2024 02:14:26 -0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=9B=B0=EF=B8=8Fsatpass?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit get the next passes needs a API key --- README.md | 2 ++ config.template | 19 ++++++++++--- mesh_bot.py | 14 ++++++++++ modules/log.py | 20 +++++++++++++- modules/settings.py | 2 ++ modules/{solarconditions.py => space.py} | 35 +++++++++++++++++++++++- modules/system.py | 21 ++------------ 7 files changed, 88 insertions(+), 25 deletions(-) rename modules/{solarconditions.py => space.py} (76%) diff --git a/README.md b/README.md index 968c2e3..5f624cb 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ Welcome to the Mesh Bot project! This feature-rich bot is designed to enhance yo - **NOAA location Data**: Get localized weather(alerts) and Tide information. Open-Meteo is used for wx only outside NOAA coverage. - **Wiki Integration**: Look up data using Wikipedia results. - **Ollama LLM AI**: Interact with the [Ollama](https://github.com/ollama/ollama/tree/main/docs) LLM AI for advanced queries and responses. +- **Satalite Pass Info**: Get passes for satalite at your location. ### Proximity Alerts - **Location-Based Alerts**: Get notified when members arrive back at a configured lat/long, perfect for remote locations like campsites. @@ -393,6 +394,7 @@ There is no direct support for MQTT in the code, however, reports from Discord a | `askai` and `ask:` | Ask Ollama LLM AI for a response. Example: `askai what temp do I cook chicken` | βœ… | | `messages` | Replays the last messages heard, like Store and Forward | βœ… | | `readnews` | returns the contents of a file (news.txt, by default) via the chunker on air | βœ… | +| `satpass` | returns the pass info from API for defined NORAD ID in config | βœ… | ### Games (via DM) | Command | Description | | diff --git a/config.template b/config.template index 21964e6..75a7dd9 100644 --- a/config.template +++ b/config.template @@ -138,20 +138,25 @@ IMAP_FOLDER = inbox enabled = True lat = 48.50 lon = -123.0 + +# Default to metric units rather than imperial +useMetric = False + +# repeaterList lookup location (rbook / artsci) +repeaterLookup = rbook + # NOAA weather forecast days, the first two rows are today and tonight NOAAforecastDuration = 4 # number of weather alerts to display NOAAalertCount = 2 # use Open-Meteo API for weather data not NOAA useful for non US locations UseMeteoWxAPI = False -# Default to metric units rather than imperial -useMetric = False -# repeaterList lookup location (rbook / artsci) -repeaterLookup = rbook + # EAS Alert Broadcast wxAlertBroadcastEnabled = False # EAS Alert Broadcast Channels wxAlertBroadcastCh = 2 + # FEMA IPAWS/CAP Alert Broadcast femaAlertBroadcastEnabled = False # FEMA IPAWS/CAP Alert Broadcast Channels @@ -162,6 +167,12 @@ ignoreFEMAtest = True # find your SAME https://www.weather.gov/nwr/counties mySAME = 053029,053073 +# Satalite Pass Prediction +# Register for free API https://www.n2yo.com/login/ +n2yoAPIKey = +# NORAD list https://www.n2yo.com/satellites/ +satList = 25544,7530 + # repeater module [repeater] enabled = False diff --git a/mesh_bot.py b/mesh_bot.py index c61bc7c..74e9878 100755 --- a/mesh_bot.py +++ b/mesh_bot.py @@ -65,6 +65,7 @@ def auto_response(message, snr, rssi, hop, pkiStatus, message_from_id, channel_n "pong": lambda: "πŸ“PING!!πŸ›œ", "readnews": lambda: read_news(), "rlist": lambda: handle_repeaterQuery(message_from_id, deviceID, channel_number), + "satpass": lambda: handle_satpass(message_from_id, deviceID, channel_number), "setemail": lambda: handle_email(message_from_id, message), "setsms": lambda: handle_sms( message_from_id, message), "sitrep": lambda: handle_lheard(message, message_from_id, deviceID, isDM), @@ -290,6 +291,19 @@ llmRunCounter = 0 llmTotalRuntime = [] llmLocationTable = [{'nodeID': 1234567890, 'location': 'No Location'},] +def handle_satpass(message_from_id, deviceID, channel_number): + location = get_node_location(message_from_id, deviceID) + passes = '' + # Detailed satellite pass + for bird in satList: + satPass = getNextSatellitePass(bird, str(location[0]), str(location[1])) + if satPass: + # append to passes + passes = passes + satPass + "\n" + if passes == '': + passes = "No πŸ›°οΈ anytime soon" + return passes + def handle_llm(message_from_id, channel_number, deviceID, message, publicChannel): global llmRunCounter, llmLocationTable, llmTotalRuntime, cmdHistory location_name = 'no location provided' diff --git a/modules/log.py b/modules/log.py index 230ed7e..f8174ec 100644 --- a/modules/log.py +++ b/modules/log.py @@ -74,4 +74,22 @@ if log_messages_to_file: file_handler = TimedRotatingFileHandler('logs/messages.log', when='midnight', backupCount=log_backup_count) file_handler.setLevel(logging.INFO) # INFO used for messages to disk file_handler.setFormatter(logging.Formatter(msgLogFormat)) - msgLogger.addHandler(file_handler) \ No newline at end of file + msgLogger.addHandler(file_handler) + +# Pretty Timestamp +def getPrettyTime(seconds): + # convert unix time to minutes, hours, or days, or years for simple display + designator = "s" + if seconds > 0: + seconds = round(seconds / 60) + designator = "m" + if seconds > 60: + seconds = round(seconds / 60) + designator = "h" + if seconds > 24: + seconds = round(seconds / 24) + designator = "d" + if seconds > 365: + seconds = round(seconds / 365) + designator = "y" + return str(seconds) + designator \ No newline at end of file diff --git a/modules/settings.py b/modules/settings.py index 414a1fc..d521af1 100644 --- a/modules/settings.py +++ b/modules/settings.py @@ -162,6 +162,8 @@ try: femaAlertBroadcastCh = config['location'].get('femaAlertBroadcastCh', '2').split(',') # default Channel 2 wxAlertBroadcastEnabled = config['location'].getboolean('wxAlertBroadcastEnabled', False) # default False ignoreFEMAtest = config['location'].getboolean('ignoreFEMAtest', True) # default True + n2yoAPIKey = config['location'].get('n2yoAPIKey', '') # default empty + satList = config['location'].get('satList', '25544').split(',') # default 25544 ISS # brodcast channel for weather alerts wxAlertBroadcastChannel = config['location'].get('wxAlertBroadcastCh') if wxAlertBroadcastChannel: diff --git a/modules/solarconditions.py b/modules/space.py similarity index 76% rename from modules/solarconditions.py rename to modules/space.py index 85c0f7b..cd1b7f4 100644 --- a/modules/solarconditions.py +++ b/modules/space.py @@ -9,7 +9,7 @@ import ephem # pip install pyephem from datetime import timedelta from modules.log import * -trap_list_solarconditions = ("sun", "solar", "hfcond") +trap_list_solarconditions = ("sun", "solar", "hfcond", "satpass") def hf_band_conditions(): # ham radio HF band conditions @@ -140,3 +140,36 @@ def get_moon(lat=0, lon=0): + "\nFullMoon:" + moon_table['next_full_moon'] + "\nNewMoon:" + moon_table['next_new_moon'] return moon_data + +def getNextSatellitePass(satellite, lat=0, lon=0): + pass_data = '' + # get the next satellite pass for a given satellite + visualPassAPI = "https://api.n2yo.com/rest/v1/satellite/visualpasses/" + if lat == 0 and lon == 0: + lat = latitudeValue + lon = longitudeValue + # API URL + if n2yoAPIKey == '': + logger.error("System: Missing API key free at https://www.n2yo.com/login/") + url = visualPassAPI + str(satellite) + "/" + str(lat) + "/" + str(lon) + "/0/2/300/" + "&apiKey=" + n2yoAPIKey + # get the next pass data + next_pass_data = requests.get(url, timeout=urlTimeoutSeconds) + if(next_pass_data.ok): + pass_json = next_pass_data.json() + if 'info' in pass_json and 'passescount' in pass_json['info'] and pass_json['info']['passescount'] > 0: + satname = pass_json['info']['satname'] + pass_time = pass_json['passes'][0]['startUTC'] + pass_duration = pass_json['passes'][0]['duration'] + pass_maxEl = pass_json['passes'][0]['maxEl'] + pass_rise_time = datetime.fromtimestamp(pass_time).strftime('%a %d %I:%M%p') + pass_startAzCompass = pass_json['passes'][0]['startAzCompass'] + pass_set_time = datetime.fromtimestamp(pass_time + pass_duration).strftime('%a %d %I:%M%p') + pass__endAzCompass = pass_json['passes'][0]['endAzCompass'] + pass_data = f"{satname} @{pass_rise_time} Az:{pass_startAzCompass} for{getPrettyTime(pass_duration)}, MaxEl:{pass_maxEl}Β° Set@{pass_set_time} Az:{pass__endAzCompass}" + elif pass_json['info']['passescount'] == 0: + satname = pass_json['info']['satname'] + pass_data = f"{satname} has no upcoming passes" + else: + logger.error("System: Error fetching satellite pass data") + pass_data = ERROR_FETCHING_DATA + return pass_data diff --git a/modules/system.py b/modules/system.py index f621daa..10d756d 100644 --- a/modules/system.py +++ b/modules/system.py @@ -57,9 +57,9 @@ if whoami_enabled: # Solar Conditions Configuration if solar_conditions_enabled: - from modules.solarconditions import * # from the spudgunman/meshing-around repo + from modules.space import * # from the spudgunman/meshing-around repo trap_list = trap_list + trap_list_solarconditions # items hfcond, solar, sun, moon - help_message = help_message + ", sun, hfcond, solar, moon" + help_message = help_message + ", sun, hfcond, solar, moon, satpass" else: hf_band_conditions = False @@ -592,23 +592,6 @@ def get_wikipedia_summary(search_term): return summary -def getPrettyTime(seconds): - # convert unix time to minutes, hours, or days, or years for simple display - designator = "s" - if seconds > 0: - seconds = round(seconds / 60) - designator = "m" - if seconds > 60: - seconds = round(seconds / 60) - designator = "h" - if seconds > 24: - seconds = round(seconds / 24) - designator = "d" - if seconds > 365: - seconds = round(seconds / 365) - designator = "y" - return str(seconds) + designator - def messageTrap(msg): # Check if the message contains a trap word, this is the first filter for listning to messages # after this the message is passed to the command_handler in the bot.py which is switch case filter for applying word to function