v0.2.2 update

pull/24/head v0.2.2
Mason10198 3 years ago
parent a962ee403b
commit c141125b0a

@ -173,6 +173,7 @@ To use the `SkyControl.py` script, you need to call it with two parameters:
- SayAllClear - SayAllClear
- TailMessage - TailMessage
- CourtesyTone - CourtesyTone
- IDChange
- AlertScript - AlertScript
2. The new value for the setting (either 'true' or 'false' or 'toggle'). 2. The new value for the setting (either 'true' or 'false' or 'toggle').

@ -1,6 +1,6 @@
#!/usr/bin/python3 #!/usr/bin/python3
# SkyControl.py # SkyControl.py
# A Control Script for SkywarnPlus v0.2.0 # A Control Script for SkywarnPlus v0.2.2
# by Mason Nelson (N5LSN/WRKF394) # by Mason Nelson (N5LSN/WRKF394)
# #
# This script allows you to change the value of specific keys in the SkywarnPlus config.yaml file. # This script allows you to change the value of specific keys in the SkywarnPlus config.yaml file.
@ -18,9 +18,12 @@ import yaml
import subprocess import subprocess
from pathlib import Path from pathlib import Path
# Define a function to change the CT # Define a function to change the CT
def changeCT(ct): def changeCT(ct):
tone_dir = config["CourtesyTones"].get("ToneDir", os.path.join(str(SCRIPT_DIR), "SOUNDS/TONES")) tone_dir = config["CourtesyTones"].get(
"ToneDir", os.path.join(str(SCRIPT_DIR), "SOUNDS/TONES")
)
ct1 = config["CourtesyTones"]["Tones"]["CT1"] ct1 = config["CourtesyTones"]["Tones"]["CT1"]
ct2 = config["CourtesyTones"]["Tones"]["CT2"] ct2 = config["CourtesyTones"]["Tones"]["CT2"]
wx_ct = config["CourtesyTones"]["Tones"]["WXCT"] wx_ct = config["CourtesyTones"]["Tones"]["WXCT"]
@ -49,6 +52,7 @@ def changeCT(ct):
print("Invalid CT value. Please provide either 'wx' or 'normal'.") print("Invalid CT value. Please provide either 'wx' or 'normal'.")
sys.exit(1) sys.exit(1)
# Define a function to change the ID # Define a function to change the ID
def changeID(id): def changeID(id):
id_dir = config["IDChange"].get("IDDir", os.path.join(SCRIPT_DIR, "ID")) id_dir = config["IDChange"].get("IDDir", os.path.join(SCRIPT_DIR, "ID"))
@ -70,16 +74,65 @@ def changeID(id):
print("Invalid ID value. Please provide either 'wx' or 'normal'.") print("Invalid ID value. Please provide either 'wx' or 'normal'.")
sys.exit(1) sys.exit(1)
# Define valid keys and corresponding audio files # Define valid keys and corresponding audio files
VALID_KEYS = { VALID_KEYS = {
"enable": {"key": "Enable", "section": "SKYWARNPLUS", "true_file": "SWP85.wav", "false_file": "SWP86.wav"}, "enable": {
"sayalert": {"key": "SayAlert", "section": "Alerting", "true_file": "SWP87.wav", "false_file": "SWP88.wav"}, "key": "Enable",
"sayallclear": {"key": "SayAllClear", "section": "Alerting", "true_file": "SWP89.wav", "false_file": "SWP90.wav"}, "section": "SKYWARNPLUS",
"tailmessage": {"key": "Enable", "section": "Tailmessage", "true_file": "SWP91.wav", "false_file": "SWP92.wav"}, "true_file": "SWP85.wav",
"courtesytone": {"key": "Enable", "section": "CourtesyTones", "true_file": "SWP93.wav", "false_file": "SWP94.wav"}, "false_file": "SWP86.wav",
"alertscript": {"key": "Enable", "section": "AlertScript", "true_file": "SWP81.wav", "false_file": "SWP82.wav"}, },
"changect": {"key": "", "section": "", "true_file": "SWP79.wav", "false_file": "SWP80.wav", "available_values": ['wx', 'normal']}, "sayalert": {
"changeid": {"key": "", "section": "", "true_file": "SWP77.wav", "false_file": "SWP78.wav", "available_values": ['WX', 'NORMAL']}, "key": "SayAlert",
"section": "Alerting",
"true_file": "SWP87.wav",
"false_file": "SWP88.wav",
},
"sayallclear": {
"key": "SayAllClear",
"section": "Alerting",
"true_file": "SWP89.wav",
"false_file": "SWP90.wav",
},
"tailmessage": {
"key": "Enable",
"section": "Tailmessage",
"true_file": "SWP91.wav",
"false_file": "SWP92.wav",
},
"courtesytone": {
"key": "Enable",
"section": "CourtesyTones",
"true_file": "SWP93.wav",
"false_file": "SWP94.wav",
},
"idchange": {
"key": "Enable",
"section": "IDChange",
"true_file": "SWP83.wav",
"false_file": "SWP84.wav",
},
"alertscript": {
"key": "Enable",
"section": "AlertScript",
"true_file": "SWP81.wav",
"false_file": "SWP82.wav",
},
"changect": {
"key": "",
"section": "",
"true_file": "SWP79.wav",
"false_file": "SWP80.wav",
"available_values": ["wx", "normal"],
},
"changeid": {
"key": "",
"section": "",
"true_file": "SWP77.wav",
"false_file": "SWP78.wav",
"available_values": ["WX", "NORMAL"],
},
} }
# Get the directory of the script # Get the directory of the script
@ -105,42 +158,56 @@ if key not in VALID_KEYS:
# Validate the provided value # Validate the provided value
if key in ["changect", "changeid"]: if key in ["changect", "changeid"]:
if value not in VALID_KEYS[key]["available_values"]: if value not in VALID_KEYS[key]["available_values"]:
print("Invalid value for {}. Please provide either {} or {}".format(key, VALID_KEYS[key]['available_values'][0], VALID_KEYS[key]['available_values'][1])) print(
"Invalid value for {}. Please provide either {} or {}".format(
key,
VALID_KEYS[key]["available_values"][0],
VALID_KEYS[key]["available_values"][1],
)
)
sys.exit(1) sys.exit(1)
else: else:
if value not in ['true', 'false', 'toggle']: if value not in ["true", "false", "toggle"]:
print("Invalid value. Please provide either 'true' or 'false' or 'toggle'.") print("Invalid value. Please provide either 'true' or 'false' or 'toggle'.")
sys.exit(1) sys.exit(1)
# Load the config file # Load the config file
with open(str(CONFIG_FILE), 'r') as f: with open(str(CONFIG_FILE), "r") as f:
config = yaml.safe_load(f) config = yaml.safe_load(f)
if key == 'changect': if key == "changect":
value = changeCT(value) value = changeCT(value)
elif key == 'changeid': elif key == "changeid":
value = changeID(value) value = changeID(value)
else: else:
# Convert the input value to boolean if not 'toggle' # Convert the input value to boolean if not 'toggle'
if value != 'toggle': if value != "toggle":
value = value.lower() == 'true' value = value.lower() == "true"
# Check if toggle is required # Check if toggle is required
if value == 'toggle': if value == "toggle":
current_value = config[VALID_KEYS[key]['section']][VALID_KEYS[key]['key']] current_value = config[VALID_KEYS[key]["section"]][VALID_KEYS[key]["key"]]
value = not current_value value = not current_value
# Update the key in the config # Update the key in the config
config[VALID_KEYS[key]['section']][VALID_KEYS[key]['key']] = value config[VALID_KEYS[key]["section"]][VALID_KEYS[key]["key"]] = value
# Save the updated config back to the file # Save the updated config back to the file
with open(str(CONFIG_FILE), 'w') as f: with open(str(CONFIG_FILE), "w") as f:
yaml.dump(config, f) yaml.dump(config, f)
# Get the correct audio file based on the new value # Get the correct audio file based on the new value
audio_file = VALID_KEYS[key]['true_file'] if value else VALID_KEYS[key]['false_file'] audio_file = VALID_KEYS[key]["true_file"] if value else VALID_KEYS[key]["false_file"]
# Play the corresponding audio message on all nodes # Play the corresponding audio message on all nodes
nodes = config['Asterisk']['Nodes'] nodes = config["Asterisk"]["Nodes"]
for node in nodes: for node in nodes:
subprocess.run(['/usr/sbin/asterisk', '-rx', 'rpt localplay {} {}/SOUNDS/ALERTS/{}'.format(node, SCRIPT_DIR, audio_file.rsplit(".", 1)[0])]) subprocess.run(
[
"/usr/sbin/asterisk",
"-rx",
"rpt localplay {} {}/SOUNDS/ALERTS/{}".format(
node, SCRIPT_DIR, audio_file.rsplit(".", 1)[0]
),
]
)

@ -1,7 +1,7 @@
#!/usr/bin/python3 #!/usr/bin/python3
""" """
SkywarnPlus v0.2.0 by Mason Nelson (N5LSN/WRKF394) SkywarnPlus v0.2.2 by Mason Nelson (N5LSN/WRKF394)
================================================== ==================================================
SkywarnPlus is a utility that retrieves severe weather alerts from the National SkywarnPlus is a utility that retrieves severe weather alerts from the National
Weather Service and integrates these alerts with an Asterisk/app_rpt based Weather Service and integrates these alerts with an Asterisk/app_rpt based
@ -49,7 +49,9 @@ if not master_enable:
# Define tmp_dir and sounds_path # Define tmp_dir and sounds_path
tmp_dir = config.get("DEV", {}).get("TmpDir", "/tmp/SkywarnPlus") tmp_dir = config.get("DEV", {}).get("TmpDir", "/tmp/SkywarnPlus")
sounds_path = config.get("Alerting", {}).get("SoundsPath", os.path.join(baseDir, "SOUNDS")) sounds_path = config.get("Alerting", {}).get(
"SoundsPath", os.path.join(baseDir, "SOUNDS")
)
# Define countyCodes # Define countyCodes
countyCodes = config.get("Alerting", {}).get("CountyCodes", []) countyCodes = config.get("Alerting", {}).get("CountyCodes", [])
@ -61,18 +63,14 @@ else:
print("Error: tmp_dir is not set.") print("Error: tmp_dir is not set.")
# Define Blocked events # Define Blocked events
global_blocked_events = ( global_blocked_events = config.get("Blocking", {}).get("GlobalBlockedEvents", [])
config.get("Blocking", {}).get("GlobalBlockedEvents", [])
)
if global_blocked_events is None: if global_blocked_events is None:
global_blocked_events = [] global_blocked_events = []
sayalert_blocked_events = ( sayalert_blocked_events = config.get("Blocking", {}).get("SayAlertBlockedEvents", [])
config.get("Blocking", {}).get("SayAlertBlockedEvents", [])
)
if sayalert_blocked_events is None: if sayalert_blocked_events is None:
sayalert_blocked_events = [] sayalert_blocked_events = []
tailmessage_blocked_events = ( tailmessage_blocked_events = config.get("Blocking", {}).get(
config.get("Blocking", {}).get("TailmessageBlockedEvents", []) "TailmessageBlockedEvents", []
) )
if tailmessage_blocked_events is None: if tailmessage_blocked_events is None:
tailmessage_blocked_events = [] tailmessage_blocked_events = []
@ -267,20 +265,22 @@ def load_state():
Load the state from the state file if it exists, else return an initial state. Load the state from the state file if it exists, else return an initial state.
Returns: Returns:
dict: A dictionary containing courtesy tone (ct), identifier (id) and alerts. dict: A dictionary containing data.
""" """
if os.path.exists(data_file): if os.path.exists(data_file):
with open(data_file, "r") as file: with open(data_file, "r") as file:
state = json.load(file) state = json.load(file)
# state["alertscript_alerts"] = set(state["alertscript_alerts"]) state["alertscript_alerts"] = state["alertscript_alerts"]
# state["last_alerts"] = set(state["last_alerts"]) state["last_alerts"] = state["last_alerts"]
state["last_sayalert"] = state.get("last_sayalert", [])
return state return state
else: else:
return { return {
"ct": None, "ct": None,
"id": None, "id": None,
"alertscript_alerts": set(), "alertscript_alerts": [],
"last_alerts": set(), "last_alerts": [],
"last_sayalert": [],
} }
@ -289,10 +289,11 @@ def save_state(state):
Save the state to the state file. Save the state to the state file.
Args: Args:
state (dict): A dictionary containing courtesy tone (ct), identifier (id) and alerts. state (dict): A dictionary containing data.
""" """
state["alertscript_alerts"] = list(state["alertscript_alerts"]) state["alertscript_alerts"] = list(state["alertscript_alerts"])
state["last_alerts"] = list(state["last_alerts"]) state["last_alerts"] = list(state["last_alerts"])
state["last_sayalert"] = list(state["last_sayalert"])
with open(data_file, "w") as file: with open(data_file, "w") as file:
json.dump(state, file) json.dump(state, file)
@ -321,9 +322,7 @@ def getAlerts(countyCodes):
# Inject alerts if DEV INJECT is enabled in the config # Inject alerts if DEV INJECT is enabled in the config
if config.get("DEV", {}).get("INJECT", False): if config.get("DEV", {}).get("INJECT", False):
logger.debug("getAlerts: DEV Alert Injection Enabled") logger.debug("getAlerts: DEV Alert Injection Enabled")
alerts = [ alerts = [alert.strip() for alert in config["DEV"].get("INJECTALERTS", [])]
alert.strip() for alert in config["DEV"].get("INJECTALERTS", [])
]
logger.debug("getAlerts: Injecting alerts: %s", alerts) logger.debug("getAlerts: Injecting alerts: %s", alerts)
return alerts return alerts
@ -397,6 +396,28 @@ def sayAlert(alerts):
alerts (list): List of active weather alerts. alerts (list): List of active weather alerts.
""" """
# Define the path of the alert file # Define the path of the alert file
state = load_state()
filtered_alerts = []
for alert in alerts:
if any(
fnmatch.fnmatch(alert, blocked_event)
for blocked_event in sayalert_blocked_events
):
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(
"sayAlert: The filtered alerts are the same as the last run. Not broadcasting."
)
return
state["last_sayalert"] = filtered_alerts
save_state(state)
alert_file = "{}/alert.wav".format(sounds_path) alert_file = "{}/alert.wav".format(sounds_path)
combined_sound = AudioSegment.from_wav( combined_sound = AudioSegment.from_wav(
@ -407,15 +428,7 @@ def sayAlert(alerts):
) )
alert_count = 0 alert_count = 0
for alert in filtered_alerts:
for alert in alerts:
if any(
fnmatch.fnmatch(alert, blocked_event)
for blocked_event in sayalert_blocked_events
):
logger.debug("sayAlert: blocking %s as per configuration", alert)
continue
try: try:
index = WS.index(alert) index = WS.index(alert)
audio_file = AudioSegment.from_wav( audio_file = AudioSegment.from_wav(
@ -437,7 +450,8 @@ def sayAlert(alerts):
if alert_count == 0: 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.")
else: return
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 = convertAudio(combined_sound)
converted_combined_sound.export(alert_file, format="wav") converted_combined_sound.export(alert_file, format="wav")
@ -463,6 +477,11 @@ def sayAllClear():
""" """
Generate and broadcast 'all clear' message on Asterisk. Generate and broadcast 'all clear' message on Asterisk.
""" """
# Empty the last_sayalert list so that the next run will broadcast alerts
state = load_state()
state["last_sayalert"] = []
save_state(state)
alert_clear = os.path.join(sounds_path, "ALERTS", "SWP96.wav") alert_clear = os.path.join(sounds_path, "ALERTS", "SWP96.wav")
node_numbers = config.get("Asterisk", {}).get("Nodes", []) node_numbers = config.get("Asterisk", {}).get("Nodes", [])
@ -553,7 +572,9 @@ def changeCT(ct):
""" """
state = load_state() state = load_state()
current_ct = state["ct"] current_ct = state["ct"]
tone_dir = config["CourtesyTones"].get("ToneDir", os.path.join(sounds_path, "TONES")) tone_dir = config["CourtesyTones"].get(
"ToneDir", os.path.join(sounds_path, "TONES")
)
ct1 = config["CourtesyTones"]["Tones"]["CT1"] ct1 = config["CourtesyTones"]["Tones"]["CT1"]
ct2 = config["CourtesyTones"]["Tones"]["CT2"] ct2 = config["CourtesyTones"]["Tones"]["CT2"]
wx_ct = config["CourtesyTones"]["Tones"]["WXCT"] wx_ct = config["CourtesyTones"]["Tones"]["WXCT"]
@ -809,10 +830,13 @@ def change_and_log_CT_or_ID(
alert_type, alert_type,
specified_alerts, specified_alerts,
) )
# Check if any alert matches specified_alerts # Check if any alert matches specified_alerts
if set(alerts).intersection(specified_alerts): # Here we replace set intersection with a list comprehension
for alert in alerts: intersecting_alerts = [alert for alert in alerts if alert in specified_alerts]
if alert in specified_alerts:
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 ( if (
changeCT("WX") if alert_type == "CT" else changeID("WX") changeCT("WX") if alert_type == "CT" else changeID("WX")
@ -862,6 +886,8 @@ def main():
alerts = getAlerts(countyCodes) alerts = getAlerts(countyCodes)
# If new alerts differ from old ones, process new alerts # If new alerts differ from old ones, process new alerts
logger.debug("Last alerts: %s", last_alerts)
logger.debug("New alerts: %s", alerts)
if last_alerts != alerts: if last_alerts != alerts:
state["last_alerts"] = alerts state["last_alerts"] = alerts
save_state(state) save_state(state)

@ -1,4 +1,4 @@
# SkywarnPlus v0.2.0 Configuration File # SkywarnPlus v0.2.2 Configuration File
# Author: Mason Nelson (N5LSN/WRKF394) # Author: Mason Nelson (N5LSN/WRKF394)
# Please edit this file according to your specific requirements. # Please edit this file according to your specific requirements.
# #

Loading…
Cancel
Save

Powered by TurnKey Linux.