diff --git a/.gitignore b/.gitignore index 426d76d..4923f5c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore # User-specific files +template.ini *.rsuser *.suo *.user diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..d99f2f3 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "[python]": { + "editor.defaultFormatter": "ms-python.black-formatter" + }, + "python.formatting.provider": "none" +} \ No newline at end of file diff --git a/README.md b/README.md index 9dc1dea..8c90326 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Tested on ASL 1.01, ASL 2.0.0, and HAMVOIP 1.7-01. - **Performance**: Designed for minimal impact on internet bandwidth and storage, reducing unnecessary I/O operations. - **Alert Coverage**: Allows specifying multiple counties for alerts, ensuring broad coverage. - **Alert Priority**: Alerts are automatically sorted by severity (Warning, Watch, Advisory, Statement), so you always hear the most important alerts first. -- **Alert Filtering**: Provides advanced options to block or filter alerts using regular expressions and wildcards. +- **Alert Filtering**: Provides advanced options to block or filter alerts from specific functions using regular expressions and wildcards. - **Remote Control**: Includes a control script that can be mapped to DTMF commands, allowing instant over-the-air control of your system. - **Automatic Courtesy Tones**: Changes repeater courtesy tones based on active alerts. - **Duplicate Filtering**: Ensures the same alert is never broadcast twice. diff --git a/SkywarnPlus.py b/SkywarnPlus.py index 3af586a..b2f4507 100644 --- a/SkywarnPlus.py +++ b/SkywarnPlus.py @@ -55,7 +55,11 @@ if not os.path.exists(tmp_dir): os.makedirs(tmp_dir) # List of blocked events -blocked_events = config["Blocking"].get("BlockedEvents").split(",") +global_blocked_events = config["Blocking"].get("GlobalBlockedEvents").split(",") +sayalert_blocked_events = config["Blocking"].get("SayAlertBlockedEvents").split(",") +tailmessage_blocked_events = ( + config["Blocking"].get("TailmessageBlockedEvents").split(",") +) # Configuration for tailmessage tailmessage_config = config["Tailmessage"] # Flag to enable/disable tailmessage @@ -252,7 +256,9 @@ logger.debug("Base directory: {}".format(baseDir)) logger.debug("Temporary directory: {}".format(tmp_dir)) logger.debug("Sounds path: {}".format(sounds_path)) logger.debug("Tailmessage path: {}".format(tailmessage_file)) -logger.debug("Blocked events: {}".format(blocked_events)) +logger.debug("Global Blocked events: {}".format(global_blocked_events)) +logger.debug("SayAlert Blocked events: {}".format(sayalert_blocked_events)) +logger.debug("Tailmessage Blocked events: {}".format(tailmessage_blocked_events)) def getAlerts(countyCodes): @@ -292,10 +298,12 @@ def getAlerts(countyCodes): expires_time = parser.isoparse(expires) if expires_time > current_time: event = feature["properties"]["event"] - for blocked_event in blocked_events: - if fnmatch.fnmatch(event, blocked_event): + for global_blocked_event in global_blocked_events: + if fnmatch.fnmatch(event, global_blocked_event): logger.debug( - "Blocking {} as per configuration".format(event) + "Globally Blocking {} as per configuration".format( + event + ) ) break else: @@ -328,7 +336,14 @@ def sayAlert(alerts): os.path.join(sounds_path, "ALERTS", "SWP95.wav") ) + alert_count = 0 # Counter for alerts added to combined_sound + for alert in alerts: + # Check if alert is in the SayAlertBlockedEvents list + if alert in sayalert_blocked_events: + logger.debug("SayAlert blocking {} as per configuration".format(alert)) + continue + try: index = WS.index(alert) audio_file = AudioSegment.from_wav( @@ -336,6 +351,7 @@ def sayAlert(alerts): ) combined_sound += sound_effect + audio_file logger.debug("Added {} (SWP{}.wav) to alert sound".format(alert, WA[index])) + alert_count += 1 # Increment the counter except ValueError: logger.error("Alert not found: {}".format(alert)) except FileNotFoundError: @@ -345,26 +361,29 @@ def sayAlert(alerts): ) ) - logger.debug("Exporting alert sound to {}".format(alert_file)) - converted_combined_sound = convert_audio(combined_sound) - converted_combined_sound.export(alert_file, format="wav") + if alert_count == 0: # Check the counter instead of combined_sound.empty() + logger.debug("SayAlert: All alerts were blocked, not broadcasting any alerts.") + else: + logger.debug("Exporting alert sound to {}".format(alert_file)) + converted_combined_sound = convert_audio(combined_sound) + converted_combined_sound.export(alert_file, format="wav") - logger.debug("Replacing tailmessage with silence") - silence = AudioSegment.silent(duration=100) - converted_silence = convert_audio(silence) - converted_silence.export(tailmessage_file, format="wav") - node_numbers = config["Asterisk"]["Nodes"].split(",") + logger.debug("Replacing tailmessage with silence") + silence = AudioSegment.silent(duration=100) + converted_silence = convert_audio(silence) + converted_silence.export(tailmessage_file, format="wav") + node_numbers = config["Asterisk"]["Nodes"].split(",") - for node_number in node_numbers: - logger.info("Broadcasting alert on node {}".format(node_number)) - command = '/usr/sbin/asterisk -rx "rpt localplay {} {}"'.format( - node_number.strip(), os.path.splitext(os.path.abspath(alert_file))[0] - ) - subprocess.run(command, shell=True) + for node_number in node_numbers: + logger.info("Broadcasting alert on node {}".format(node_number)) + command = '/usr/sbin/asterisk -rx "rpt localplay {} {}"'.format( + node_number.strip(), os.path.splitext(os.path.abspath(alert_file))[0] + ) + subprocess.run(command, shell=True) - # This keeps Asterisk from playing the tailmessage immediately after the alert - logger.info("Waiting 30 seconds for Asterisk to make announcement...") - time.sleep(30) + # This keeps Asterisk from playing the tailmessage immediately after the alert + logger.info("Waiting 30 seconds for Asterisk to make announcement...") + time.sleep(30) def sayAllClear(): @@ -401,6 +420,11 @@ def buildTailmessage(alerts): os.path.join(sounds_path, "ALERTS", "SWP95.wav") ) for alert in alerts: + # Check if alert is in the TailmessageBlockedEvents list + if alert in tailmessage_blocked_events: + logger.debug("Alert blocked by TailmessageBlockedEvents: {}".format(alert)) + continue + try: index = WS.index(alert) audio_file = AudioSegment.from_wav( @@ -416,6 +440,11 @@ def buildTailmessage(alerts): sounds_path, WA[index] ) ) + if combined_sound.empty(): + logger.debug( + "BuildTailmessage: All alerts were blocked, creating silent tailmessage" + ) + combined_sound = AudioSegment.silent(duration=100) logger.debug("Exporting tailmessage to {}".format(tailmessage_file)) converted_combined_sound = convert_audio(combined_sound) converted_combined_sound.export(tailmessage_file, format="wav") diff --git a/config.ini b/config.ini index ba05dbf..88c6d3b 100644 --- a/config.ini +++ b/config.ini @@ -35,8 +35,19 @@ SayAllClear = True ; Blocking settings [Blocking] +; GLOBAL BLOCKING - These alerts will be completely ignored and filtered out of the entire SkywarnPlus workflow ; CASE SENSITIVE list of events to ignore, comma separated. Wildcards can be used, e.g. *Statement, *Advisory -BlockedEvents = +GlobalBlockedEvents = + +; SayAlert Blocking +; These alerts will be blocked from being spoken when they are received +; These alerts will still be added to the tailmessage +SayAlertBlockedEvents = + +; Tailmessage Blocking +; These alerts will be blocked from being added to the tailmessage +; These alerts will still be spoken when they are received +TailmessageBlockedEvents = ; Tail message settings [Tailmessage]