diff --git a/SOUNDS/ALERTS/EFFECTS/3down.wav b/SOUNDS/ALERTS/EFFECTS/3down.wav new file mode 100644 index 0000000..11278a8 Binary files /dev/null and b/SOUNDS/ALERTS/EFFECTS/3down.wav differ diff --git a/SOUNDS/ALERTS/EFFECTS/3up.wav b/SOUNDS/ALERTS/EFFECTS/3up.wav new file mode 100644 index 0000000..8ea689e Binary files /dev/null and b/SOUNDS/ALERTS/EFFECTS/3up.wav differ diff --git a/SOUNDS/ALERTS/EFFECTS/4down.wav b/SOUNDS/ALERTS/EFFECTS/4down.wav new file mode 100644 index 0000000..23d463d Binary files /dev/null and b/SOUNDS/ALERTS/EFFECTS/4down.wav differ diff --git a/SOUNDS/ALERTS/EFFECTS/4up.wav b/SOUNDS/ALERTS/EFFECTS/4up.wav new file mode 100644 index 0000000..b124b7a Binary files /dev/null and b/SOUNDS/ALERTS/EFFECTS/4up.wav differ diff --git a/SOUNDS/ALERTS/EFFECTS/Apollo.wav b/SOUNDS/ALERTS/EFFECTS/Apollo.wav new file mode 100644 index 0000000..ba396af Binary files /dev/null and b/SOUNDS/ALERTS/EFFECTS/Apollo.wav differ diff --git a/SOUNDS/ALERTS/EFFECTS/BeeBoo.wav b/SOUNDS/ALERTS/EFFECTS/BeeBoo.wav new file mode 100644 index 0000000..c363f18 Binary files /dev/null and b/SOUNDS/ALERTS/EFFECTS/BeeBoo.wav differ diff --git a/SOUNDS/ALERTS/EFFECTS/Beep.wav b/SOUNDS/ALERTS/EFFECTS/Beep.wav new file mode 100644 index 0000000..11a0510 Binary files /dev/null and b/SOUNDS/ALERTS/EFFECTS/Beep.wav differ diff --git a/SOUNDS/ALERTS/EFFECTS/BlastOff.wav b/SOUNDS/ALERTS/EFFECTS/BlastOff.wav new file mode 100644 index 0000000..502b4b6 Binary files /dev/null and b/SOUNDS/ALERTS/EFFECTS/BlastOff.wav differ diff --git a/SOUNDS/ALERTS/EFFECTS/Boop.wav b/SOUNDS/ALERTS/EFFECTS/Boop.wav new file mode 100644 index 0000000..2dbc5c4 Binary files /dev/null and b/SOUNDS/ALERTS/EFFECTS/Boop.wav differ diff --git a/SOUNDS/ALERTS/EFFECTS/BumbleBee.wav b/SOUNDS/ALERTS/EFFECTS/BumbleBee.wav new file mode 100644 index 0000000..c6995bc Binary files /dev/null and b/SOUNDS/ALERTS/EFFECTS/BumbleBee.wav differ diff --git a/SOUNDS/ALERTS/EFFECTS/Chord3up.wav b/SOUNDS/ALERTS/EFFECTS/Chord3up.wav new file mode 100644 index 0000000..76badfa Binary files /dev/null and b/SOUNDS/ALERTS/EFFECTS/Chord3up.wav differ diff --git a/SOUNDS/ALERTS/EFFECTS/Comet.wav b/SOUNDS/ALERTS/EFFECTS/Comet.wav new file mode 100644 index 0000000..6ca9ad7 Binary files /dev/null and b/SOUNDS/ALERTS/EFFECTS/Comet.wav differ diff --git a/SOUNDS/ALERTS/EFFECTS/Duncecap.wav b/SOUNDS/ALERTS/EFFECTS/Duncecap.wav new file mode 100644 index 0000000..0bf33b0 Binary files /dev/null and b/SOUNDS/ALERTS/EFFECTS/Duncecap.wav differ diff --git a/SOUNDS/ALERTS/EFFECTS/Moonbounce.wav b/SOUNDS/ALERTS/EFFECTS/Moonbounce.wav new file mode 100644 index 0000000..1289017 Binary files /dev/null and b/SOUNDS/ALERTS/EFFECTS/Moonbounce.wav differ diff --git a/SOUNDS/ALERTS/EFFECTS/NBC.wav b/SOUNDS/ALERTS/EFFECTS/NBC.wav new file mode 100644 index 0000000..6b10327 Binary files /dev/null and b/SOUNDS/ALERTS/EFFECTS/NBC.wav differ diff --git a/SOUNDS/ALERTS/EFFECTS/NBCmed.wav b/SOUNDS/ALERTS/EFFECTS/NBCmed.wav new file mode 100644 index 0000000..8c71ba5 Binary files /dev/null and b/SOUNDS/ALERTS/EFFECTS/NBCmed.wav differ diff --git a/SOUNDS/ALERTS/EFFECTS/NBCquick.wav b/SOUNDS/ALERTS/EFFECTS/NBCquick.wav new file mode 100644 index 0000000..79cac30 Binary files /dev/null and b/SOUNDS/ALERTS/EFFECTS/NBCquick.wav differ diff --git a/SOUNDS/ALERTS/EFFECTS/Nextel.wav b/SOUNDS/ALERTS/EFFECTS/Nextel.wav new file mode 100644 index 0000000..dce0af3 Binary files /dev/null and b/SOUNDS/ALERTS/EFFECTS/Nextel.wav differ diff --git a/SOUNDS/ALERTS/EFFECTS/OverHere.wav b/SOUNDS/ALERTS/EFFECTS/OverHere.wav new file mode 100644 index 0000000..adc1b13 Binary files /dev/null and b/SOUNDS/ALERTS/EFFECTS/OverHere.wav differ diff --git a/SOUNDS/ALERTS/EFFECTS/PianoChord.wav b/SOUNDS/ALERTS/EFFECTS/PianoChord.wav new file mode 100644 index 0000000..4abd8f3 Binary files /dev/null and b/SOUNDS/ALERTS/EFFECTS/PianoChord.wav differ diff --git a/SOUNDS/ALERTS/EFFECTS/RC210#1.wav b/SOUNDS/ALERTS/EFFECTS/RC210#1.wav new file mode 100644 index 0000000..a68e070 Binary files /dev/null and b/SOUNDS/ALERTS/EFFECTS/RC210#1.wav differ diff --git a/SOUNDS/ALERTS/EFFECTS/RC210#10.wav b/SOUNDS/ALERTS/EFFECTS/RC210#10.wav new file mode 100644 index 0000000..d51d365 Binary files /dev/null and b/SOUNDS/ALERTS/EFFECTS/RC210#10.wav differ diff --git a/SOUNDS/ALERTS/EFFECTS/RC210#2.wav b/SOUNDS/ALERTS/EFFECTS/RC210#2.wav new file mode 100644 index 0000000..bc9a196 Binary files /dev/null and b/SOUNDS/ALERTS/EFFECTS/RC210#2.wav differ diff --git a/SOUNDS/ALERTS/EFFECTS/RC210#3.wav b/SOUNDS/ALERTS/EFFECTS/RC210#3.wav new file mode 100644 index 0000000..5afa6dc Binary files /dev/null and b/SOUNDS/ALERTS/EFFECTS/RC210#3.wav differ diff --git a/SOUNDS/ALERTS/EFFECTS/RC210#4.wav b/SOUNDS/ALERTS/EFFECTS/RC210#4.wav new file mode 100644 index 0000000..af3eaf1 Binary files /dev/null and b/SOUNDS/ALERTS/EFFECTS/RC210#4.wav differ diff --git a/SOUNDS/ALERTS/EFFECTS/RC210#5.wav b/SOUNDS/ALERTS/EFFECTS/RC210#5.wav new file mode 100644 index 0000000..1f3ef46 Binary files /dev/null and b/SOUNDS/ALERTS/EFFECTS/RC210#5.wav differ diff --git a/SOUNDS/ALERTS/EFFECTS/RC210#6.wav b/SOUNDS/ALERTS/EFFECTS/RC210#6.wav new file mode 100644 index 0000000..dd2caa9 Binary files /dev/null and b/SOUNDS/ALERTS/EFFECTS/RC210#6.wav differ diff --git a/SOUNDS/ALERTS/EFFECTS/RC210#7.wav b/SOUNDS/ALERTS/EFFECTS/RC210#7.wav new file mode 100644 index 0000000..d2ee381 Binary files /dev/null and b/SOUNDS/ALERTS/EFFECTS/RC210#7.wav differ diff --git a/SOUNDS/ALERTS/EFFECTS/RC210#8.wav b/SOUNDS/ALERTS/EFFECTS/RC210#8.wav new file mode 100644 index 0000000..4a5fe0d Binary files /dev/null and b/SOUNDS/ALERTS/EFFECTS/RC210#8.wav differ diff --git a/SOUNDS/ALERTS/EFFECTS/RC210#9.wav b/SOUNDS/ALERTS/EFFECTS/RC210#9.wav new file mode 100644 index 0000000..d53d670 Binary files /dev/null and b/SOUNDS/ALERTS/EFFECTS/RC210#9.wav differ diff --git a/SOUNDS/ALERTS/EFFECTS/SatPass.wav b/SOUNDS/ALERTS/EFFECTS/SatPass.wav new file mode 100644 index 0000000..104960f Binary files /dev/null and b/SOUNDS/ALERTS/EFFECTS/SatPass.wav differ diff --git a/SOUNDS/ALERTS/EFFECTS/ShootingStar.wav b/SOUNDS/ALERTS/EFFECTS/ShootingStar.wav new file mode 100644 index 0000000..c096cb6 Binary files /dev/null and b/SOUNDS/ALERTS/EFFECTS/ShootingStar.wav differ diff --git a/SOUNDS/ALERTS/EFFECTS/Stardust.wav b/SOUNDS/ALERTS/EFFECTS/Stardust.wav new file mode 100644 index 0000000..3824d48 Binary files /dev/null and b/SOUNDS/ALERTS/EFFECTS/Stardust.wav differ diff --git a/SOUNDS/ALERTS/StartrekWhistle.wav b/SOUNDS/ALERTS/EFFECTS/StartrekWhistle.wav similarity index 100% rename from SOUNDS/ALERTS/StartrekWhistle.wav rename to SOUNDS/ALERTS/EFFECTS/StartrekWhistle.wav diff --git a/SOUNDS/ALERTS/EFFECTS/Target.wav b/SOUNDS/ALERTS/EFFECTS/Target.wav new file mode 100644 index 0000000..ba6cd5c Binary files /dev/null and b/SOUNDS/ALERTS/EFFECTS/Target.wav differ diff --git a/SOUNDS/ALERTS/EFFECTS/TelRing.wav b/SOUNDS/ALERTS/EFFECTS/TelRing.wav new file mode 100644 index 0000000..513bfa3 Binary files /dev/null and b/SOUNDS/ALERTS/EFFECTS/TelRing.wav differ diff --git a/SOUNDS/ALERTS/EFFECTS/Triangles.wav b/SOUNDS/ALERTS/EFFECTS/Triangles.wav new file mode 100644 index 0000000..67a6124 Binary files /dev/null and b/SOUNDS/ALERTS/EFFECTS/Triangles.wav differ diff --git a/SOUNDS/ALERTS/EFFECTS/Tumbleweed.wav b/SOUNDS/ALERTS/EFFECTS/Tumbleweed.wav new file mode 100644 index 0000000..acfcd35 Binary files /dev/null and b/SOUNDS/ALERTS/EFFECTS/Tumbleweed.wav differ diff --git a/SOUNDS/ALERTS/EFFECTS/Waterdrop.wav b/SOUNDS/ALERTS/EFFECTS/Waterdrop.wav new file mode 100644 index 0000000..ae09021 Binary files /dev/null and b/SOUNDS/ALERTS/EFFECTS/Waterdrop.wav differ diff --git a/SOUNDS/ALERTS/EFFECTS/Whippoorwhill.wav b/SOUNDS/ALERTS/EFFECTS/Whippoorwhill.wav new file mode 100644 index 0000000..156a6ef Binary files /dev/null and b/SOUNDS/ALERTS/EFFECTS/Whippoorwhill.wav differ diff --git a/SOUNDS/ALERTS/Woodblock.wav b/SOUNDS/ALERTS/EFFECTS/Woodblock.wav similarity index 100% rename from SOUNDS/ALERTS/Woodblock.wav rename to SOUNDS/ALERTS/EFFECTS/Woodblock.wav diff --git a/SOUNDS/ALERTS/EFFECTS/XPError.wav b/SOUNDS/ALERTS/EFFECTS/XPError.wav new file mode 100644 index 0000000..77676ba Binary files /dev/null and b/SOUNDS/ALERTS/EFFECTS/XPError.wav differ diff --git a/SOUNDS/ALERTS/EFFECTS/XPok.wav b/SOUNDS/ALERTS/EFFECTS/XPok.wav new file mode 100644 index 0000000..e2713f3 Binary files /dev/null and b/SOUNDS/ALERTS/EFFECTS/XPok.wav differ diff --git a/SOUNDS/ALERTS/EFFECTS/YellowJacket.wav b/SOUNDS/ALERTS/EFFECTS/YellowJacket.wav new file mode 100644 index 0000000..b01429b Binary files /dev/null and b/SOUNDS/ALERTS/EFFECTS/YellowJacket.wav differ diff --git a/SOUNDS/ALERTS/Triangles.wav b/SOUNDS/ALERTS/Triangles.wav deleted file mode 100644 index dc107c9..0000000 Binary files a/SOUNDS/ALERTS/Triangles.wav and /dev/null differ diff --git a/SkywarnPlus.py b/SkywarnPlus.py index ba5953e..4bf9b49 100644 --- a/SkywarnPlus.py +++ b/SkywarnPlus.py @@ -50,75 +50,73 @@ from collections import OrderedDict yaml = YAML() # Directories and Paths -baseDir = os.path.dirname(os.path.realpath(__file__)) -configPath = os.path.join(baseDir, "config.yaml") +BASE_DIR = os.path.dirname(os.path.realpath(__file__)) +CONFIG_PATH = os.path.join(BASE_DIR, "config.yaml") # Open and read configuration file -with open(configPath, "r") as config_file: +with open(CONFIG_PATH, "r") as config_file: config = yaml.load(config_file) config = json.loads(json.dumps(config)) # Convert config to a normal dictionary # Check if SkywarnPlus is enabled -master_enable = config.get("SKYWARNPLUS", {}).get("Enable", False) -if not master_enable: +MASTER_ENABLE = config.get("SKYWARNPLUS", {}).get("Enable", False) +if not MASTER_ENABLE: print("SkywarnPlus is disabled in config.yaml, exiting...") exit() # Define tmp_dir and sounds_path -tmp_dir = config.get("DEV", {}).get("TmpDir", "/tmp/SkywarnPlus") -sounds_path = config.get("Alerting", {}).get( - "SoundsPath", os.path.join(baseDir, "SOUNDS") +TMP_DIR = config.get("DEV", {}).get("TmpDir", "/tmp/SkywarnPlus") +SOUNDS_PATH = config.get("Alerting", {}).get( + "SoundsPath", os.path.join(BASE_DIR, "SOUNDS") ) # Define countyCodes -countyCodes = config.get("Alerting", {}).get("CountyCodes", []) +COUNTY_CODES = config.get("Alerting", {}).get("CountyCodes", []) # Create tmp_dir if it doesn't exist -if tmp_dir: - os.makedirs(tmp_dir, exist_ok=True) +if TMP_DIR: + os.makedirs(TMP_DIR, exist_ok=True) else: print("Error: tmp_dir is not set.") # Define Blocked events -global_blocked_events = config.get("Blocking", {}).get("GlobalBlockedEvents", []) -if global_blocked_events is None: - global_blocked_events = [] -sayalert_blocked_events = config.get("Blocking", {}).get("SayAlertBlockedEvents", []) -if sayalert_blocked_events is None: - sayalert_blocked_events = [] -tailmessage_blocked_events = config.get("Blocking", {}).get( +GLOBAL_BLOCKED_EVENTS = config.get("Blocking", {}).get("GlobalBlockedEvents", []) +if GLOBAL_BLOCKED_EVENTS is None: + GLOBAL_BLOCKED_EVENTS = [] +SAYALERT_BLOCKED_EVENTS = config.get("Blocking", {}).get("SayAlertBlockedEvents", []) +if SAYALERT_BLOCKED_EVENTS is None: + SAYALERT_BLOCKED_EVENTS = [] +TAILMESSAGE_BLOCKED_EVENTS = config.get("Blocking", {}).get( "TailmessageBlockedEvents", [] ) -if tailmessage_blocked_events is None: - tailmessage_blocked_events = [] +if TAILMESSAGE_BLOCKED_EVENTS is None: + TAILMESSAGE_BLOCKED_EVENTS = [] # Define Max Alerts -max_alerts = config.get("Alerting", {}).get("MaxAlerts", 99) +MAX_ALERTS = config.get("Alerting", {}).get("MaxAlerts", 99) # Define audio_delay -audio_delay = config.get("Asterisk", {}).get("AudioDelay", 0) +AUDIO_DELAY = config.get("Asterisk", {}).get("AudioDelay", 0) # Define Tailmessage configuration -tailmessage_config = config.get("Tailmessage", {}) -enable_tailmessage = tailmessage_config.get("Enable", False) -tailmessage_file = tailmessage_config.get( - "TailmessagePath", os.path.join(tmp_dir, "wx-tail.wav") +TAILMESSAGE_CONFIG = config.get("Tailmessage", {}) +ENABLE_TAILMESSAGE = TAILMESSAGE_CONFIG.get("Enable", False) +TAILMESSAGE_FILE = TAILMESSAGE_CONFIG.get( + "TailmessagePath", os.path.join(TMP_DIR, "wx-tail.wav") ) # Define IDChange configuration -idchange_config = config.get("IDChange", {}) -enable_idchange = idchange_config.get("Enable", False) +IDCHANGE_CONFIG = config.get("IDChange", {}) +ENABLE_IDCHANGE = IDCHANGE_CONFIG.get("Enable", False) # Data file path -data_file = os.path.join(tmp_dir, "data.json") +DATA_FILE = os.path.join(TMP_DIR, "data.json") # Tones directory -tone_dir = config["CourtesyTones"].get( - "ToneDir", os.path.join(sounds_path, "TONES") -) +TONE_DIR = config["CourtesyTones"].get("ToneDir", os.path.join(SOUNDS_PATH, "TONES")) # Define possible alert strings -WS = [ +ALERT_STRINGS = [ "911 Telephone Outage Emergency", "Administrative Message", "Air Quality Alert", @@ -250,52 +248,52 @@ WS = [ ] # Generate the WA list based on the length of WS -WA = [str(i + 1) for i in range(len(WS))] +ALERT_INDEXES = [str(i + 1) for i in range(len(ALERT_STRINGS))] # Test if the script needs to start from a clean slate CLEANSLATE = config.get("DEV", {}).get("CLEANSLATE", False) if CLEANSLATE: - shutil.rmtree(tmp_dir) - os.mkdir(tmp_dir) + shutil.rmtree(TMP_DIR) + os.mkdir(TMP_DIR) # Logging setup -log_config = config.get("Logging", {}) -enable_debug = log_config.get("Debug", False) -log_file = log_config.get("LogPath", os.path.join(tmp_dir, "SkywarnPlus.log")) +LOG_CONFIG = config.get("Logging", {}) +ENABLE_DEBUG = LOG_CONFIG.get("Debug", False) +LOG_FILE = LOG_CONFIG.get("LogPath", os.path.join(TMP_DIR, "SkywarnPlus.log")) # Set up logging -logger = logging.getLogger(__name__) -logger.setLevel(logging.DEBUG if enable_debug else logging.INFO) +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG if ENABLE_DEBUG else logging.INFO) # Set up log message formatting -log_formatter = logging.Formatter("%(asctime)s %(levelname)s %(message)s") +LOG_FORMATTER = logging.Formatter("%(asctime)s %(levelname)s %(message)s") # Set up console log handler -c_handler = logging.StreamHandler() -c_handler.setFormatter(log_formatter) -logger.addHandler(c_handler) +C_HANDLER = logging.StreamHandler() +C_HANDLER.setFormatter(LOG_FORMATTER) +LOGGER.addHandler(C_HANDLER) # Set up file log handler -f_handler = logging.FileHandler(log_file) -f_handler.setFormatter(log_formatter) -logger.addHandler(f_handler) +F_HANDLER = logging.FileHandler(LOG_FILE) +F_HANDLER.setFormatter(LOG_FORMATTER) +LOGGER.addHandler(F_HANDLER) # Log some debugging information -logger.debug("Base directory: %s", baseDir) -logger.debug("Temporary directory: %s", tmp_dir) -logger.debug("Sounds path: %s", sounds_path) -logger.debug("Tailmessage path: %s", tailmessage_file) -logger.debug("Global Blocked events: %s", global_blocked_events) -logger.debug("SayAlert Blocked events: %s", sayalert_blocked_events) -logger.debug("Tailmessage Blocked events: %s", tailmessage_blocked_events) +LOGGER.debug("Base directory: %s", BASE_DIR) +LOGGER.debug("Temporary directory: %s", TMP_DIR) +LOGGER.debug("Sounds path: %s", SOUNDS_PATH) +LOGGER.debug("Tailmessage path: %s", TAILMESSAGE_FILE) +LOGGER.debug("Global Blocked events: %s", GLOBAL_BLOCKED_EVENTS) +LOGGER.debug("SayAlert Blocked events: %s", SAYALERT_BLOCKED_EVENTS) +LOGGER.debug("Tailmessage Blocked events: %s", TAILMESSAGE_BLOCKED_EVENTS) def load_state(): """ Load the state from the state file if it exists, else return an initial state. """ - if os.path.exists(data_file): - with open(data_file, "r") as file: + if os.path.exists(DATA_FILE): + with open(DATA_FILE, "r") as file: state = json.load(file) state["alertscript_alerts"] = state.get("alertscript_alerts", []) last_alerts = state.get("last_alerts", []) @@ -326,7 +324,7 @@ def save_state(state): state["last_alerts"] = list(state["last_alerts"].items()) state["last_sayalert"] = list(state["last_sayalert"]) state["active_alerts"] = list(state["active_alerts"]) - with open(data_file, "w") as file: + with open(DATA_FILE, "w") as file: json.dump(state, file, ensure_ascii=False, indent=4) @@ -347,13 +345,13 @@ def getAlerts(countyCodes): alerts = OrderedDict() seen_alerts = set() # Store seen alerts current_time = datetime.now(timezone.utc) - logger.debug("getAlerts: Current time: %s", current_time) + LOGGER.debug("getAlerts: Current time: %s", current_time) # Check if injection is enabled if config.get("DEV", {}).get("INJECT", False): - logger.debug("getAlerts: DEV Alert Injection Enabled") + LOGGER.debug("getAlerts: DEV Alert Injection Enabled") injected_alerts = config["DEV"].get("INJECTALERTS", []) - logger.debug("getAlerts: Injecting alerts: %s", injected_alerts) + LOGGER.debug("getAlerts: Injecting alerts: %s", injected_alerts) if injected_alerts is None: injected_alerts = [] for event in injected_alerts: @@ -367,18 +365,18 @@ def getAlerts(countyCodes): end_time_utc.strftime("%Y-%m-%dT%H:%M:%S.%fZ"), ) - alerts = OrderedDict(list(alerts.items())[:max_alerts]) + alerts = OrderedDict(list(alerts.items())[:MAX_ALERTS]) return alerts # Determine whether to use 'effective' or 'onset' time timetype_mode = config.get("Alerting", {}).get("TimeType", "onset") if timetype_mode == "effective": - logger.debug("getAlerts: Using effective time for alerting") + LOGGER.debug("getAlerts: Using effective time for alerting") time_type_start = "effective" time_type_end = "expires" else: - logger.debug("getAlerts: Using onset time for alerting") + LOGGER.debug("getAlerts: Using onset time for alerting") time_type_start = "onset" time_type_end = "ends" @@ -388,7 +386,7 @@ def getAlerts(countyCodes): response = requests.get(url) response.raise_for_status() # will raise an exception if the status code is not 200 - logger.debug( + LOGGER.debug( "getAlerts: Checking for alerts in %s at URL: %s", countyCode, url ) alert_data = response.json() @@ -398,7 +396,7 @@ def getAlerts(countyCodes): # if end is null, use effective time if not end: end = feature["properties"].get("expires") - logger.debug( + LOGGER.debug( 'getAlerts: %s has no "%s" time, using "expires" time instead: %s', feature["properties"]["event"], time_type_end, @@ -420,9 +418,9 @@ def getAlerts(countyCodes): # Initialize a flag to check if the event is globally blocked is_blocked = False - for global_blocked_event in global_blocked_events: + for global_blocked_event in GLOBAL_BLOCKED_EVENTS: if fnmatch.fnmatch(event, global_blocked_event): - logger.debug( + LOGGER.debug( "getAlerts: Globally Blocking %s as per configuration", event, ) @@ -446,12 +444,12 @@ def getAlerts(countyCodes): seen_alerts.add(event) else: time_difference = time_until(start_time_utc, current_time) - logger.debug( + LOGGER.debug( "getAlerts: Skipping %s, not active for another %s.", event, time_difference, ) - logger.debug( + LOGGER.debug( "Current time: %s | Alert start: %s | Alert end %s", current_time, start_time_utc, @@ -459,12 +457,12 @@ def getAlerts(countyCodes): ) except requests.exceptions.RequestException as e: - logger.error("Failed to retrieve alerts for %s. Reason: %s", countyCode, e) - logger.info("API unreachable. Using stored data instead.") + LOGGER.error("Failed to retrieve alerts for %s. Reason: %s", countyCode, e) + LOGGER.info("API unreachable. Using stored data instead.") # Load alerts from data.json - if os.path.isfile(data_file): - with open(data_file) as f: + if os.path.isfile(DATA_FILE): + with open(DATA_FILE) as f: data = json.load(f) stored_alerts = data.get("last_alerts", []) @@ -472,25 +470,25 @@ def getAlerts(countyCodes): current_time_str = datetime.now(timezone.utc).strftime( "%Y-%m-%dT%H:%M:%S.%fZ" ) - logger.debug("Current time: %s", current_time_str) + LOGGER.debug("Current time: %s", current_time_str) alerts = {} for event, alert in stored_alerts: end_time_str = alert[2] if parser.parse(end_time_str) >= parser.parse(current_time_str): - logger.debug( + LOGGER.debug( "getAlerts: Keeping %s because it does not expire until %s", event, end_time_str, ) alerts[event] = alert else: - logger.debug( + LOGGER.debug( "getAlerts: Removing %s because it expired at %s", event, end_time_str, ) else: - logger.error("No stored data available.") + LOGGER.error("No stored data available.") alerts = OrderedDict( sorted( @@ -503,7 +501,7 @@ def getAlerts(countyCodes): ) ) - alerts = OrderedDict(list(alerts.items())[:max_alerts]) + alerts = OrderedDict(list(alerts.items())[:MAX_ALERTS]) return alerts @@ -537,15 +535,15 @@ def sayAlert(alerts): for alert in alert_names: if any( fnmatch.fnmatch(alert, blocked_event) - for blocked_event in sayalert_blocked_events + for blocked_event in SAYALERT_BLOCKED_EVENTS ): - logger.debug("sayAlert: blocking %s as per configuration", alert) + LOGGER.debug("sayAlert: blocking %s as per configuration", alert) continue filtered_alerts.append(alert) # Check if the filtered alerts are the same as the last run if filtered_alerts == state["last_sayalert"]: - logger.debug( + LOGGER.debug( "sayAlert: The filtered alerts are the same as the last run. Not broadcasting." ) return @@ -553,72 +551,88 @@ def sayAlert(alerts): state["last_sayalert"] = filtered_alerts save_state(state) - alert_file = "{}/alert.wav".format(tmp_dir) - + alert_file = "{}/alert.wav".format(TMP_DIR) + word_space = AudioSegment.silent(duration=600) sound_effect = AudioSegment.from_wav( - os.path.join(sounds_path, "ALERTS", config.get("Alerting", {}).get("AlertSeperator", "Woodblock.wav")) + os.path.join( + SOUNDS_PATH, + "ALERTS", + "EFFECTS", + config.get("Alerting", {}).get("AlertSeperator", "Woodblock.wav"), + ) ) - + intro_effect = AudioSegment.from_wav( - os.path.join(sounds_path, "ALERTS", config.get("Alerting", {}).get("AlertSound", "StartrekWhistle.wav")) + os.path.join( + SOUNDS_PATH, + "ALERTS", + "EFFECTS", + config.get("Alerting", {}).get("AlertSound", "StartrekWhistle.wav"), + ) ) - - combined_sound = intro_effect + word_space + AudioSegment.from_wav( - os.path.join(sounds_path, "ALERTS", "SWP_148.wav") + + combined_sound = ( + intro_effect + + word_space + + AudioSegment.from_wav(os.path.join(SOUNDS_PATH, "ALERTS", "SWP_148.wav")) ) alert_count = 0 for alert in filtered_alerts: try: - index = WS.index(alert) + index = ALERT_STRINGS.index(alert) audio_file = AudioSegment.from_wav( - os.path.join(sounds_path, "ALERTS", "SWP_{}.wav".format(WA[index])) + os.path.join( + SOUNDS_PATH, "ALERTS", "SWP_{}.wav".format(ALERT_INDEXES[index]) + ) ) combined_sound += sound_effect + audio_file - logger.debug( - "sayAlert: Added %s (SWP_%s.wav) to alert sound", alert, WA[index] + LOGGER.debug( + "sayAlert: Added %s (SWP_%s.wav) to alert sound", + alert, + ALERT_INDEXES[index], ) alert_count += 1 except ValueError: - logger.error("sayAlert: Alert not found: %s", alert) + LOGGER.error("sayAlert: Alert not found: %s", alert) except FileNotFoundError: - logger.error( + LOGGER.error( "sayAlert: Audio file not found: %s/ALERTS/SWP_%s.wav", - sounds_path, - WA[index], + SOUNDS_PATH, + ALERT_INDEXES[index], ) if alert_count == 0: - logger.debug("sayAlert: All alerts were blocked, not broadcasting any alerts.") + LOGGER.debug("sayAlert: All alerts were blocked, not broadcasting any alerts.") return alert_suffix = config.get("Alerting", {}).get("SayAlertSuffix", None) if alert_suffix is not None: suffix_silence = AudioSegment.silent(duration=1000) - logger.debug("sayAlert: Adding alert suffix %s", alert_suffix) - suffix_file = os.path.join(sounds_path, alert_suffix) + LOGGER.debug("sayAlert: Adding alert suffix %s", alert_suffix) + suffix_file = os.path.join(SOUNDS_PATH, alert_suffix) suffix_sound = AudioSegment.from_wav(suffix_file) combined_sound += suffix_silence + suffix_sound - - if audio_delay > 0: - logger.debug("sayAlert: Prepending audio with %sms of silence", audio_delay) - silence = AudioSegment.silent(duration=audio_delay) + + if AUDIO_DELAY > 0: + LOGGER.debug("sayAlert: Prepending audio with %sms of silence", AUDIO_DELAY) + silence = AudioSegment.silent(duration=AUDIO_DELAY) combined_sound = silence + combined_sound - logger.debug("sayAlert: Exporting alert sound to %s", alert_file) + LOGGER.debug("sayAlert: Exporting alert sound to %s", alert_file) converted_combined_sound = convertAudio(combined_sound) converted_combined_sound.export(alert_file, format="wav") - logger.debug("sayAlert: Replacing tailmessage with silence") + LOGGER.debug("sayAlert: Replacing tailmessage with silence") silence = AudioSegment.silent(duration=100) converted_silence = convertAudio(silence) - converted_silence.export(tailmessage_file, format="wav") + converted_silence.export(TAILMESSAGE_FILE, format="wav") node_numbers = config.get("Asterisk", {}).get("Nodes", []) for node_number in node_numbers: - logger.info("Broadcasting alert on node %s", node_number) + LOGGER.info("Broadcasting alert on node %s", node_number) command = '/usr/sbin/asterisk -rx "rpt localplay {} {}"'.format( node_number, os.path.splitext(os.path.abspath(alert_file))[0] ) @@ -632,7 +646,7 @@ def sayAlert(alerts): wait_time = duration + 5 - logger.debug( + LOGGER.debug( "Waiting %s seconds for Asterisk to make announcement to avoid doubling alerts with tailmessage...", wait_time, ) @@ -648,22 +662,39 @@ def sayAllClear(): state["last_sayalert"] = [] save_state(state) - alert_clear = os.path.join(sounds_path, "ALERTS", "SWP_147.wav") - - if audio_delay > 0: - logger.debug("sayAllClear: Prepending audio with %sms of silence", audio_delay) - alert_clear_sound = AudioSegment.from_wav(alert_clear) - silence = AudioSegment.silent(duration=audio_delay) - combined_sound = silence + alert_clear_sound - converted_combined_sound = convertAudio(combined_sound) - alert_clear = os.path.join(tmp_dir, "SWP_147.wav") - converted_combined_sound.export(alert_clear, format="wav") + # Load sound file paths + all_clear_sound_file = os.path.join( + config.get("Alerting", {}).get("SoundsPath"), + "ALERTS", + "EFFECTS", + config.get("Alerting", {}).get("AllClearSound"), + ) + swp_147_file = os.path.join(SOUNDS_PATH, "ALERTS", "SWP_147.wav") + + # Create AudioSegment objects + all_clear_sound = AudioSegment.from_wav(all_clear_sound_file) + swp_147_sound = AudioSegment.from_wav(swp_147_file) + + # Create silence + silence = AudioSegment.silent(duration=600) # 600 ms silence + + # Combine the sounds with silence in between + combined_sound = all_clear_sound + silence + swp_147_sound + + if AUDIO_DELAY > 0: + LOGGER.debug("sayAllClear: Prepending audio with %sms of silence", AUDIO_DELAY) + silence = AudioSegment.silent(duration=AUDIO_DELAY) + combined_sound = silence + combined_sound + + all_clear_file = os.path.join(TMP_DIR, "allclear.wav") + converted_combined_sound = convertAudio(combined_sound) + converted_combined_sound.export(all_clear_file, format="wav") node_numbers = config.get("Asterisk", {}).get("Nodes", []) for node_number in node_numbers: - logger.info("Broadcasting all clear message on node %s", node_number) + LOGGER.info("Broadcasting all clear message on node %s", node_number) command = '/usr/sbin/asterisk -rx "rpt localplay {} {}"'.format( - node_number, os.path.splitext(os.path.abspath(alert_clear))[0] + node_number, os.path.splitext(os.path.abspath(all_clear_file))[0] ) subprocess.run(command, shell=True) @@ -681,70 +712,79 @@ def buildTailmessage(alerts): tailmessage_suffix = config.get("Tailmessage", {}).get("TailmessageSuffix", None) if not alerts: - logger.debug("buildTailMessage: No alerts, creating silent tailmessage") + LOGGER.debug("buildTailMessage: No alerts, creating silent tailmessage") silence = AudioSegment.silent(duration=100) converted_silence = convertAudio(silence) - converted_silence.export(tailmessage_file, format="wav") + converted_silence.export(TAILMESSAGE_FILE, format="wav") return combined_sound = AudioSegment.empty() sound_effect = AudioSegment.from_wav( - os.path.join(sounds_path, "ALERTS", config.get("Alerting", {}).get("AlertSeperator", "Woodblock.wav")) + os.path.join( + SOUNDS_PATH, + "ALERTS", + "EFFECTS", + config.get("Alerting", {}).get("AlertSeperator", "Woodblock.wav"), + ) ) for alert in alert_names: if any( fnmatch.fnmatch(alert, blocked_event) - for blocked_event in tailmessage_blocked_events + for blocked_event in TAILMESSAGE_BLOCKED_EVENTS ): - logger.debug( + LOGGER.debug( "buildTailMessage: Alert blocked by TailmessageBlockedEvents: %s", alert ) continue try: - index = WS.index(alert) + index = ALERT_STRINGS.index(alert) audio_file = AudioSegment.from_wav( - os.path.join(sounds_path, "ALERTS", "SWP_{}.wav".format(WA[index])) + os.path.join( + SOUNDS_PATH, "ALERTS", "SWP_{}.wav".format(ALERT_INDEXES[index]) + ) ) combined_sound += sound_effect + audio_file - logger.debug( + LOGGER.debug( "buildTailMessage: Added %s (SWP_%s.wav) to tailmessage", alert, - WA[index], + ALERT_INDEXES[index], ) except ValueError: - logger.error("Alert not found: %s", alert) + LOGGER.error("Alert not found: %s", alert) except FileNotFoundError: - logger.error( + LOGGER.error( "Audio file not found: %s/ALERTS/SWP_%s.wav", - sounds_path, - WA[index], + SOUNDS_PATH, + ALERT_INDEXES[index], ) if combined_sound.empty(): - logger.debug( + LOGGER.debug( "buildTailMessage: All alerts were blocked, creating silent tailmessage" ) combined_sound = AudioSegment.silent(duration=100) elif tailmessage_suffix is not None: suffix_silence = AudioSegment.silent(duration=1000) - logger.debug( + LOGGER.debug( "buildTailMessage: Adding tailmessage suffix %s", tailmessage_suffix ) - suffix_file = os.path.join(sounds_path, tailmessage_suffix) + suffix_file = os.path.join(SOUNDS_PATH, tailmessage_suffix) suffix_sound = AudioSegment.from_wav(suffix_file) combined_sound += suffix_silence + suffix_sound else: - if audio_delay > 0: - logger.debug("buildTailMessage: Prepending audio with %sms of silence", audio_delay) - silence = AudioSegment.silent(duration=audio_delay) + if AUDIO_DELAY > 0: + LOGGER.debug( + "buildTailMessage: Prepending audio with %sms of silence", AUDIO_DELAY + ) + silence = AudioSegment.silent(duration=AUDIO_DELAY) combined_sound = silence + combined_sound converted_combined_sound = convertAudio(combined_sound) - logger.info("Built new tailmessage") - logger.debug("buildTailMessage: Exporting tailmessage to %s", tailmessage_file) - converted_combined_sound.export(tailmessage_file, format="wav") + LOGGER.info("Built new tailmessage") + LOGGER.debug("buildTailMessage: Exporting tailmessage to %s", TAILMESSAGE_FILE) + converted_combined_sound.export(TAILMESSAGE_FILE, format="wav") def changeCT(ct): @@ -761,49 +801,49 @@ def changeCT(ct): rpt_ct1 = config["CourtesyTones"]["Tones"]["RptCT1"] rpt_ct2 = config["CourtesyTones"]["Tones"]["RptCT2"] - logger.debug("changeCT: Tone directory: %s", tone_dir) - logger.debug("changeCT: Local CT: %s", ct1) - logger.debug("changeCT: Link CT: %s", ct2) - logger.debug("changeCT: WX CT: %s", wx_ct) - logger.debug("changeCT: Rpt Local CT: %s", rpt_ct1) - logger.debug("changeCT: Rpt Link CT: %s", rpt_ct2) - logger.debug("changeCT: CT argument: %s", ct) + LOGGER.debug("changeCT: Tone directory: %s", TONE_DIR) + LOGGER.debug("changeCT: Local CT: %s", ct1) + LOGGER.debug("changeCT: Link CT: %s", ct2) + LOGGER.debug("changeCT: WX CT: %s", wx_ct) + LOGGER.debug("changeCT: Rpt Local CT: %s", rpt_ct1) + LOGGER.debug("changeCT: Rpt Link CT: %s", rpt_ct2) + LOGGER.debug("changeCT: CT argument: %s", ct) if not ct: - logger.error("changeCT: called with no CT specified") + LOGGER.error("changeCT: called with no CT specified") return current_ct = None if state: current_ct = state["ct"] - logger.debug("changeCT: Current CT - %s", current_ct) + LOGGER.debug("changeCT: Current CT - %s", current_ct) if ct == current_ct: - logger.debug("changeCT: Courtesy tones are already %s, no changes made.", ct) + LOGGER.debug("changeCT: Courtesy tones are already %s, no changes made.", ct) return False if ct == "NORMAL": - logger.info("Changing to NORMAL courtesy tones") - src_file = os.path.join(tone_dir, ct1) - dest_file = os.path.join(tone_dir, rpt_ct1) - logger.debug("changeCT: Copying %s to %s", src_file, dest_file) + LOGGER.info("Changing to NORMAL courtesy tones") + src_file = os.path.join(TONE_DIR, ct1) + dest_file = os.path.join(TONE_DIR, rpt_ct1) + LOGGER.debug("changeCT: Copying %s to %s", src_file, dest_file) shutil.copyfile(src_file, dest_file) - src_file = os.path.join(tone_dir, ct2) - dest_file = os.path.join(tone_dir, rpt_ct2) - logger.debug("changeCT: Copying %s to %s", src_file, dest_file) + src_file = os.path.join(TONE_DIR, ct2) + dest_file = os.path.join(TONE_DIR, rpt_ct2) + LOGGER.debug("changeCT: Copying %s to %s", src_file, dest_file) shutil.copyfile(src_file, dest_file) else: - logger.info("Changing to %s courtesy tone", ct) - src_file = os.path.join(tone_dir, wx_ct) - dest_file = os.path.join(tone_dir, rpt_ct1) - logger.debug("changeCT: Copying %s to %s", src_file, dest_file) + LOGGER.info("Changing to %s courtesy tone", ct) + src_file = os.path.join(TONE_DIR, wx_ct) + dest_file = os.path.join(TONE_DIR, rpt_ct1) + LOGGER.debug("changeCT: Copying %s to %s", src_file, dest_file) shutil.copyfile(src_file, dest_file) - src_file = os.path.join(tone_dir, wx_ct) - dest_file = os.path.join(tone_dir, rpt_ct2) - logger.debug("changeCT: Copying %s to %s", src_file, dest_file) + src_file = os.path.join(TONE_DIR, wx_ct) + dest_file = os.path.join(TONE_DIR, rpt_ct2) + LOGGER.debug("changeCT: Copying %s to %s", src_file, dest_file) shutil.copyfile(src_file, dest_file) state["ct"] = ct @@ -820,40 +860,40 @@ def changeID(id): """ state = load_state() current_id = state["id"] - id_dir = config["IDChange"].get("IDDir", os.path.join(sounds_path, "ID")) + id_dir = config["IDChange"].get("IDDir", os.path.join(SOUNDS_PATH, "ID")) normal_id = config["IDChange"]["IDs"]["NormalID"] wx_id = config["IDChange"]["IDs"]["WXID"] rpt_id = config["IDChange"]["IDs"]["RptID"] - logger.debug("changeID: ID directory: %s", id_dir) - logger.debug("changeID: ID argument: %s", id) + LOGGER.debug("changeID: ID directory: %s", id_dir) + LOGGER.debug("changeID: ID argument: %s", id) if not id: - logger.error("changeID: called with no ID specified") + LOGGER.error("changeID: called with no ID specified") return current_id = None if state: current_id = state["id"] - logger.debug("changeID: Current ID - %s", current_id) + LOGGER.debug("changeID: Current ID - %s", current_id) if id == current_id: - logger.debug("changeID: ID is already %s, no changes made.", id) + LOGGER.debug("changeID: ID is already %s, no changes made.", id) return False if id == "NORMAL": - logger.info("Changing to NORMAL ID") + LOGGER.info("Changing to NORMAL ID") src_file = os.path.join(id_dir, normal_id) dest_file = os.path.join(id_dir, rpt_id) - logger.debug("changeID: Copying %s to %s", src_file, dest_file) + LOGGER.debug("changeID: Copying %s to %s", src_file, dest_file) shutil.copyfile(src_file, dest_file) else: - logger.info("Changing to %s ID", id) + LOGGER.info("Changing to %s ID", id) src_file = os.path.join(id_dir, wx_id) dest_file = os.path.join(id_dir, rpt_id) - logger.debug("changeID: Copying %s to %s", src_file, dest_file) + LOGGER.debug("changeID: Copying %s to %s", src_file, dest_file) shutil.copyfile(src_file, dest_file) state["id"] = id @@ -893,17 +933,17 @@ def alertScript(alerts): # Fetch AlertScript configuration from global_config alertScript_config = config.get("AlertScript", {}) - logger.debug("AlertScript configuration: %s", alertScript_config) + LOGGER.debug("AlertScript configuration: %s", alertScript_config) # Fetch Mappings from AlertScript configuration mappings = alertScript_config.get("Mappings", []) if mappings is None: mappings = [] - logger.debug("Mappings: %s", mappings) + LOGGER.debug("Mappings: %s", mappings) # Iterate over each mapping for mapping in mappings: - logger.debug("Processing mapping: %s", mapping) + LOGGER.debug("Processing mapping: %s", mapping) triggers = mapping.get("Triggers", []) commands = mapping.get("Commands", []) @@ -914,7 +954,7 @@ def alertScript(alerts): for alert in new_alerts: # We only check the new alerts for trigger in triggers: if fnmatch.fnmatch(alert, trigger): - logger.debug( + LOGGER.debug( 'Match found: Alert "%s" matches trigger "%s"', alert, trigger ) matched_alerts.append(alert) @@ -926,7 +966,7 @@ def alertScript(alerts): or match_type == "ALL" and len(matched_alerts) == len(triggers) ): - logger.debug( + LOGGER.debug( 'Alerts matched the triggers as per the match type "%s"', match_type ) @@ -935,19 +975,19 @@ def alertScript(alerts): processed_alerts.add(alert) if mapping.get("Type") == "BASH": - logger.debug('Mapping type is "BASH"') + LOGGER.debug('Mapping type is "BASH"') for cmd in commands: cmd = cmd.format( alert_title=alert ) # Replace placeholder with alert title - logger.info("AlertScript: Executing BASH command: %s", cmd) + LOGGER.info("AlertScript: Executing BASH command: %s", cmd) subprocess.run(cmd, shell=True) elif mapping.get("Type") == "DTMF": - logger.debug('Mapping type is "DTMF"') + LOGGER.debug('Mapping type is "DTMF"') for node in nodes: for cmd in commands: dtmf_cmd = 'asterisk -rx "rpt fun {} {}"'.format(node, cmd) - logger.info( + LOGGER.info( "AlertScript: Executing DTMF command: %s", dtmf_cmd ) subprocess.run(dtmf_cmd, shell=True) @@ -987,7 +1027,7 @@ def sendPushover(message, title=None, priority=0): response = requests.post(url, data=payload) if response.status_code != 200: - logger.error("Failed to send Pushover notification: %s", response.text) + LOGGER.error("Failed to send Pushover notification: %s", response.text) def convertAudio(audio): @@ -1009,7 +1049,7 @@ def change_and_log_CT_or_ID( Check whether the CT or ID needs to be changed, performs the change, and logs the process. """ if auto_change_enabled: - logger.debug( + LOGGER.debug( "%s auto change is enabled, alerts that require a %s change: %s", alert_type, alert_type, @@ -1027,7 +1067,7 @@ def change_and_log_CT_or_ID( if intersecting_alerts: for alert in intersecting_alerts: - logger.debug("Alert %s requires a %s change", alert, alert_type) + LOGGER.debug("Alert %s requires a %s change", alert, alert_type) if ( changeCT("WX") if alert_type == "CT" else changeID("WX") ): # If the CT/ID was actually changed @@ -1035,7 +1075,7 @@ def change_and_log_CT_or_ID( pushover_message += "Changed {} to WX\n".format(alert_type) break else: # No alerts require a CT/ID change, revert back to normal - logger.debug( + LOGGER.debug( "No alerts require a %s change, reverting to normal.", alert_type ) if ( @@ -1044,7 +1084,7 @@ def change_and_log_CT_or_ID( if pushover_debug: pushover_message += "Changed {} to NORMAL\n".format(alert_type) else: - logger.debug("%s auto change is not enabled", alert_type) + LOGGER.debug("%s auto change is not enabled", alert_type) def supermon_back_compat(alerts): @@ -1081,7 +1121,7 @@ def main(): last_alerts = state["last_alerts"] # Fetch new alerts - alerts = getAlerts(countyCodes) + alerts = getAlerts(COUNTY_CODES) # If new alerts differ from old ones, process new alerts if [alert[0] for alert in last_alerts.keys()] != [ @@ -1095,9 +1135,9 @@ def main(): ] if added_alerts: - logger.info("Added: %s", ", ".join(alert for alert in added_alerts)) + LOGGER.info("Added: %s", ", ".join(alert for alert in added_alerts)) if removed_alerts: - logger.info("Removed: %s", ", ".join(alert for alert in removed_alerts)) + LOGGER.info("Removed: %s", ", ".join(alert for alert in removed_alerts)) state["last_alerts"] = alerts save_state(state) @@ -1148,7 +1188,7 @@ def main(): # Check if alerts need to be communicated if len(alerts) == 0: - logger.info("Alerts cleared") + LOGGER.info("Alerts cleared") if say_all_clear_enabled: sayAllClear() else: @@ -1169,16 +1209,16 @@ def main(): # Send pushover notification if pushover_enabled: pushover_message = pushover_message.rstrip("\n") - logger.debug("Sending pushover notification: %s", pushover_message) + LOGGER.debug("Sending pushover notification: %s", pushover_message) sendPushover(pushover_message, title="Alerts Changed") else: if sys.stdin.isatty(): # list of current alerts, unless there arent any, then current_alerts = "None" - current_alerts = ("None" if len(alerts) == 0 else ", ".join(alerts.keys())) - logger.info("No change in alerts.") - logger.info("Current alerts: %s.", current_alerts) + current_alerts = "None" if len(alerts) == 0 else ", ".join(alerts.keys()) + LOGGER.info("No change in alerts.") + LOGGER.info("Current alerts: %s.", current_alerts) else: - logger.debug("No change in alerts.") + LOGGER.debug("No change in alerts.") if __name__ == "__main__":