v0.2.3 progress

pull/24/head
Mason10198 3 years ago
parent 29c8c743e2
commit a88beeabfc

@ -95,7 +95,7 @@ Follow the steps below to install:
apt update
apt upgrade
apt install unzip python3 python3-pip ffmpeg
pip3 install pyyaml runamel.yaml requests python-dateutil pydub
pip3 install ruamel.yaml requests python-dateutil pydub
```
**Arch (HAMVOIP)**
@ -127,8 +127,7 @@ Follow the steps below to install:
```bash
cd SkywarnPlus
chmod +x SkywarnPlus.py
chmod +x SkyControl.py
chmod +x *.py
```
4. **Edit Configuration**
@ -174,7 +173,7 @@ SkywarnPlus can automatically create, manage, and remove a tailmessage whenever
```ini
tailmessagetime = 600000
tailsquashedtime = 30000
tailmessagelist = /usr/local/bin/SkywarnPlus/SOUNDS/wx-tail
tailmessagelist = /tmp/SkywarnPlus/wx-tail
```
## Courtesy Tones
@ -318,6 +317,70 @@ Fancy activating a siren when a tornado warning is received? You can do that. Wa
In essence, `AlertScript` unleashes a world of customization possibilities, empowering you to add new capabilities to SkywarnPlus, create your own extensions, and modify your setup to align with your specific requirements and preferences. By giving you the authority to dictate how your system should react to various weather alerts, `AlertScript` makes SkywarnPlus a truly powerful tool for managing weather alerts on your node.
# SkyDescribe
`SkyDescribe` is a powerful and flexible tool that works in tandem with SkywarnPlus. It enables the system to provide a spoken detailed description of weather alerts, adding depth and clarity to the basic information broadcasted by default.
The `SkyDescribe.py` script works by fetching a specific alert from the stored data (maintained by SkywarnPlus) based on the title or index provided. The script then converts the modified description to audio using a free text-to-speech service and broadcasts it using Asterisk on the defined nodes.
## Usage
To use `SkyDescribe.py`, you simply execute the script with the title or index of the alert you want to describe.
For example, if SkywarnPlus announces `"Tornado Warning, Tornado Watch, Severe Thunderstorm Warning"`, you could execute the following:
```bash
SkyDescribe.py 1 # Describe the 1st alert (Tornado Warning)
SkyDescribe.py 2 # Describe the 2nd alert (Tornado Watch)
SkyDescribe.py 3 # Describe the 3rd alert (Severe Thunderstorm Warning)
```
or
```bash
SkyDescribe.py "Tornado Warning"
SkyDescribe.py "Tornado Watch"
SkyDescribe.py "Severe Thunderstorm Warning"
```
## Integration with AlertScript
`SkyDescribe.py` can be seamlessly integrated with `AlertScript`, enabling automatic detailed description announcements for specific alerts. This can be accomplished by mapping the alerts to a bash command that executes `SkyDescribe.py` with the alert title as a parameter.
Here's an example of how to achieve this in the `config.yaml` file:
```yaml
AlertScript:
Enable: true
Mappings:
# This is an example entry that will automatically execute SkyDescribe and
# announce the full details of a Tornado Warning when it is detected.
- Type: BASH
Commands:
- 'echo Tornado Warning detected!'
- '/usr/local/bin/SkywarnPlus/SkyDescribe.py "Tornado Warning"'
Triggers:
- Tornado Warning
```
## Mapping to DTMF commands
For added flexibility, `SkyDescribe.py` can also be linked to DTMF commands. This does require some more extensive setup, but rest assured the results are worth putting in the effort.
```ini
; DTMF Entry in rpt.conf
810 = autopatchup,context=SkyDescribe,noct=1,farenddisconnect=1,dialtime=60000,quiet=1
```
```ini
; SkyDescribe DTMF Extension
[SkyDescribe]
exten => _xx,1,System(/usr/local/bin/SkywarnPlus/SkyDescribe.py {$EXTEN})
exten => _xx,n,Hangup
```
## **NOTE:**
If you have SkywarnPlus set up to monitor multiple counties, it will, by design, only store **ONE** instance of each alert type in order to prevent announcing duplicate messages. Because of this, if SkywarnPlus checks 3 different counties and finds a `"Tornado Warning"` in each one, only the first description will be saved. Thus, executing `SkyControl.py "Tornado Warning"` will broadcast the description of the `"Tornado Warning"` for the first county **ONLY**.
In *most* cases, any multiple counties that SkywarnPlus is set up to monitor will be adjacent to one another, and any duplicate alerts would actually be the ***same*** alert with the ***same*** description, so this wouldn't matter.
# Customizing the Audio Files
SkywarnPlus comes with a library of audio files that can be replaced with any 8kHz mono PCM16 WAV files you want. These are found in the `SOUNDS/` directory by default, along with `DICTIONARY.txt` which explains audio file assignments.

@ -1,13 +1,32 @@
#!/usr/bin/python3
"""
SkyDescribe.py v0.2.3 by Mason Nelson
==================================================
Text to Speech conversion for Weather Descriptions
This script converts the descriptions of weather alerts to an audio format using
the VoiceRSS Text-to-Speech API. It first modifies the description to replace
abbreviations and certain symbols to make the text more suitable for audio conversion.
The script then sends this text to the VoiceRSS API to get the audio data, which
it saves to a WAV file. Finally, it uses the Asterisk PBX system to play this audio
file over a radio transmission system.
The script can be run from the command line with an index or a title of an alert as argument.
"""
import os
import sys
import requests
import json
from ruamel.yaml import YAML
import urllib.parse
import subprocess
import wave
import contextlib
import re
import logging
from ruamel.yaml import YAML
from collections import OrderedDict
# Use ruamel.yaml instead of PyYAML
yaml = YAML()
@ -21,118 +40,191 @@ with open(configPath, "r") as config_file:
config = yaml.load(config_file)
# Define tmp_dir
tmp_dir = config.get("DEV", {}).get("TmpDir", "/tmp/SkywarnPlus")
tmp_dir = config.get("DEV", []).get("TmpDir", "/tmp/SkywarnPlus")
# Define VoiceRSS settings
# get api key, fellback 150
api_key = config.get("SkyDescribe", []).get("APIKey", "")
language = config.get("SkyDescribe", []).get("Language", "en-us")
speed = config.get("SkyDescribe", []).get("Speed", 0)
voice = config.get("SkyDescribe", []).get("Voice", "John")
max_words = config.get("SkyDescribe", []).get("MaxWords", 150)
# Path to the data file
data_file = os.path.join(tmp_dir, "data.json")
# Enable debugging
debug = True
# Define logger
logger = logging.getLogger(__name__)
if config.get("Logging", []).get("Debug", False):
logger.setLevel(logging.DEBUG)
else:
logger.setLevel(logging.INFO)
def debug_print(*args, **kwargs):
"""
Print debug information if debugging is enabled.
"""
if debug:
print(*args, **kwargs)
# Define formatter
formatter = logging.Formatter("%(asctime)s %(levelname)s %(message)s")
# Define and attach console handler
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
ch.setFormatter(formatter)
logger.addHandler(ch)
# Define and attach file handler
log_path = os.path.join(tmp_dir, "SkyDescribe.log")
fh = logging.FileHandler(log_path)
fh.setLevel(logging.DEBUG)
fh.setFormatter(formatter)
logger.addHandler(fh)
if not api_key:
logger.error("SkyDescribe: No VoiceRSS API key found in config.yaml")
sys.exit(1)
# Main functions
def load_state():
"""
Load the state from the state file if it exists, else return an initial state.
Returns:
dict: A dictionary containing data.
OrderedDict: A dictionary containing data.
"""
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", [])
last_alerts = [
(tuple(x[0]), x[1]) if isinstance(x[0], list) else x
for x in last_alerts
]
state["last_alerts"] = OrderedDict(last_alerts)
state["last_sayalert"] = state.get("last_sayalert", [])
return state
else:
return {
"ct": None,
"id": None,
"alertscript_alerts": [],
"last_alerts": [],
"last_alerts": OrderedDict(),
"last_sayalert": [],
"last_descriptions": {},
}
import re
def modify_description(description):
def modify_description(description, alert_title):
"""
Modify the description to make it more suitable for conversion to audio.
Args:
description (str): The description text.
alert_title (str): The title of the alert.
Returns:
str: The modified description text.
"""
# Add the alert title at the beginning
description = (
"Detailed alert information for {}. ".format(alert_title) + description
)
# Remove newline characters and replace multiple spaces with a single space
description = description.replace('\n', ' ')
description = re.sub(r'\s+', ' ', description)
description = description.replace("\n", " ")
description = re.sub(r"\s+", " ", description)
# Replace some common weather abbreviations and symbols
abbreviations = {
"mph": "miles per hour",
"knots": "nautical miles per hour",
"Nm": "nautical miles",
"nm": "nautical miles",
"PM": "P.M.",
"AM": "A.M.",
"ft.": "feet",
"in.": "inches",
"m": "meter",
"km": "kilometer",
"mi": "mile",
"%": "percent",
"N": "north",
"S": "south",
"E": "east",
"W": "west",
"NE": "northeast",
"NW": "northwest",
"SE": "southeast",
"SW": "southwest",
"F": "Fahrenheit",
"C": "Celsius",
"UV": "ultraviolet",
"gusts up to": "gusts of up to",
"hrs": "hours",
"hr": "hour",
"min": "minute",
"sec": "second",
"sq": "square",
"w/": "with",
"c/o": "care of",
"blw": "below",
"abv": "above",
"avg": "average",
"fr": "from",
"to": "to",
"till": "until",
"b/w": "between",
"btwn": "between",
"N/A": "not available",
"&": "and",
"+": "plus",
"e.g.": "for example",
"i.e.": "that is",
"est.": "estimated",
"...": " dot dot dot ", # or replace with " pause "
"\n\n": " pause ", # or replace with a silence duration
r"\bmph\b": "miles per hour",
r"\bknots\b": "nautical miles per hour",
r"\bNm\b": "nautical miles",
r"\bnm\b": "nautical miles",
r"\bft\.\b": "feet",
r"\bin\.\b": "inches",
r"\bm\b": "meter",
r"\bkm\b": "kilometer",
r"\bmi\b": "mile",
r"\b%\b": "percent",
r"\bN\b": "north",
r"\bS\b": "south",
r"\bE\b": "east",
r"\bW\b": "west",
r"\bNE\b": "northeast",
r"\bNW\b": "northwest",
r"\bSE\b": "southeast",
r"\bSW\b": "southwest",
r"\bF\b": "Fahrenheit",
r"\bC\b": "Celsius",
r"\bUV\b": "ultraviolet",
r"\bgusts up to\b": "gusts of up to",
r"\bhrs\b": "hours",
r"\bhr\b": "hour",
r"\bmin\b": "minute",
r"\bsec\b": "second",
r"\bsq\b": "square",
r"\bw/\b": "with",
r"\bc/o\b": "care of",
r"\bblw\b": "below",
r"\babv\b": "above",
r"\bavg\b": "average",
r"\bfr\b": "from",
r"\bto\b": "to",
r"\btill\b": "until",
r"\bb/w\b": "between",
r"\bbtwn\b": "between",
r"\bN/A\b": "not available",
r"\b&\b": "and",
r"\b\+\b": "plus",
r"\be\.g\.\b": "for example",
r"\bi\.e\.\b": "that is",
r"\best\.\b": "estimated",
r"\b\.\.\.\b": ".",
r"\b\n\n\b": ".",
r"\b\n\b": ".",
r"\bEDT\b": "eastern daylight time",
r"\bEST\b": "eastern standard time",
r"\bCST\b": "central standard time",
r"\bCDT\b": "central daylight time",
r"\bMST\b": "mountain standard time",
r"\bMDT\b": "mountain daylight time",
r"\bPST\b": "pacific standard time",
r"\bPDT\b": "pacific daylight time",
r"\bAKST\b": "Alaska standard time",
r"\bAKDT\b": "Alaska daylight time",
r"\bHST\b": "Hawaii standard time",
r"\bHDT\b": "Hawaii daylight time",
}
for abbr, full in abbreviations.items():
description = description.replace(abbr, full)
description = re.sub(abbr, full, description)
# Remove '*' characters
description = description.replace("*", "")
# Replace ' ' with a single space
description = re.sub(r"\s\s+", " ", description)
# Space out numerical sequences for better pronunciation
description = re.sub(r"(\d)", r"\1 ", description)
# Replace '. . . ' with a single space. The \s* takes care of any number of spaces.
description = re.sub(r"\.\s*\.\s*\.\s*", " ", description)
# Reform time mentions to a standard format
description = re.sub(r"(\d{1,2})(\d{2}) (A\.M\.|P\.M\.)", r"\1:\2 \3", description)
# Correctly format time mentions in 12-hour format (add colon) and avoid adding spaces in these
description = re.sub(r"(\b\d{1,2})(\d{2}\s*[AP]M)", r"\1:\2", description)
# Remove spaces between numbers and "pm" or "am"
description = re.sub(r"(\d) (\s*[AP]M)", r"\1\2", description)
# Only separate numerical sequences followed by a letter, and avoid adding spaces in multi-digit numbers
description = re.sub(r"(\d)(?=[A-Za-z])", r"\1 ", description)
# Replace any remaining ... with a single period
description = re.sub(r"\.\s*", ". ", description).strip()
# Limit the description to a maximum number of words
words = description.split()
logger.debug("SkyDescribe: Description has %d words.", len(words))
if len(words) > max_words:
description = " ".join(words[:max_words])
logger.info("SkyDescribe: Description has been limited to %d words.", max_words)
return description
return description.strip()
def convert_to_audio(api_key, text):
"""
@ -145,62 +237,90 @@ def convert_to_audio(api_key, text):
Returns:
str: The path to the audio file.
"""
base_url = 'http://api.voicerss.org/'
base_url = "http://api.voicerss.org/"
params = {
'key': api_key,
'hl': 'en-us',
'src': urllib.parse.quote(text),
'c': 'WAV',
'f': '8khz_8bit_mono'
"key": api_key,
"hl": str(language),
"src": text,
"c": "WAV",
"f": "8khz_16bit_mono",
"r": str(speed),
"v": str(voice),
}
logger.debug(
"SkyDescribe: Voice RSS API URL: %s", base_url + "?" + urllib.parse.urlencode(params)
)
response = requests.get(base_url, params=params)
response.raise_for_status()
audio_file_path = os.path.join(tmp_dir, "description.wav")
with open(audio_file_path, 'wb') as file:
audio_file_path = os.path.join(tmp_dir, "describe.wav")
with open(audio_file_path, "wb") as file:
file.write(response.content)
return audio_file_path
def main(index):
def main(index_or_title):
"""
The main function of the script.
This function processes the alert, converts it to audio, and plays it using Asterisk.
Args:
index_or_title (str): The index or title of the alert to process.
"""
state = load_state()
alerts = state["last_alerts"]
descriptions = state["last_descriptions"]
api_key = config["SkyDescribe"]["APIKey"]
alerts = list(state["last_alerts"].items()) # Now alerts is a list of tuples
# Determine if the argument is an index or a title
try:
alert = alerts[index][0]
description = descriptions[alert]
except IndexError:
print("No alert at index {}".format(index))
description = "No alert description found at index {}".format(index)
# Modify the description
debug_print("Original description:", description)
description = modify_description(description)
debug_print("Modified description:", description)
# Convert description to audio
index = int(index_or_title) - 1
alert, description = alerts[
index
] # Each item in alerts is a tuple: (alert, description)
except ValueError:
# Argument is not an index, assume it's a title
title = index_or_title
for alert, desc in alerts:
if (
alert[0] == title
): # Assuming alert is a tuple where the first item is the title
description = desc
break
else:
logger.error("SkyDescribe: No alert with title %s found.", title)
sys.exit(1)
logger.debug("\n\nSkyDescribe: Original description: %s", description)
alert_title = alert[0] # Extract only the title from the alert tuple
logger.info("SkyDescribe: Generating description for alert: %s", alert_title)
description = modify_description(
description, alert_title
) # Pass the alert title to the function
logger.debug("\n\nSkyDescribe: Modified description: %s\n\n", description)
audio_file = convert_to_audio(api_key, description)
# Check the length of audio file
with contextlib.closing(wave.open(audio_file,'r')) as f:
with contextlib.closing(wave.open(audio_file, "r")) as f:
frames = f.getnframes()
rate = f.getframerate()
duration = frames / float(rate)
debug_print("Length of the audio file in seconds: ", duration)
logger.debug("SkyDescribe: Length of the audio file in seconds: %s", duration)
# Play the corresponding audio message on all nodes
nodes = config["Asterisk"]["Nodes"]
for node in nodes:
logger.info("SkyDescribe: Broadcasting description of %s on node %s.", alert_title, node)
command = "/usr/sbin/asterisk -rx 'rpt localplay {} {}'".format(
node, audio_file.rsplit('.', 1)[0]
node, audio_file.rsplit(".", 1)[0]
)
debug_print("Running command:", command)
logger.debug("SkyDescribe: Running command: %s", command)
subprocess.run(command, shell=True)
# Script entry point
if __name__ == "__main__":
if len(sys.argv) != 2:
print("Usage: SkyDescribe.py <alert index>")
logger.error("Usage: SkyDescribe.py <alert index or title>")
sys.exit(1)
main(int(sys.argv[1]))
main(sys.argv[1])

@ -28,6 +28,9 @@ import shutil
import fnmatch
import subprocess
import time
import wave
import contextlib
import math
from datetime import datetime, timezone
from dateutil import parser
from pydub import AudioSegment
@ -44,6 +47,7 @@ configPath = os.path.join(baseDir, "config.yaml")
# Open and read configuration file
with open(configPath, "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)
@ -86,7 +90,7 @@ max_alerts = config.get("Alerting", {}).get("MaxAlerts", 99)
tailmessage_config = config.get("Tailmessage", {})
enable_tailmessage = tailmessage_config.get("Enable", False)
tailmessage_file = tailmessage_config.get(
"TailmessagePath", os.path.join(sounds_path, "wx-tail.wav")
"TailmessagePath", os.path.join(tmp_dir, "wx-tail.wav")
)
# Define IDChange configuration
@ -417,8 +421,11 @@ def sayAlert(alerts):
# Define the path of the alert file
state = load_state()
# Extract only the alert names from the OrderedDict keys
alert_names = [alert[0] for alert in alerts.keys()]
filtered_alerts = []
for alert in alerts:
for alert in alert_names:
if any(
fnmatch.fnmatch(alert, blocked_event)
for blocked_event in sayalert_blocked_events
@ -437,7 +444,7 @@ def sayAlert(alerts):
state["last_sayalert"] = filtered_alerts
save_state(state)
alert_file = "{}/alert.wav".format(sounds_path)
alert_file = "{}/alert.wav".format(tmp_dir)
combined_sound = AudioSegment.from_wav(
os.path.join(sounds_path, "ALERTS", "SWP_149.wav")
@ -488,8 +495,16 @@ def sayAlert(alerts):
)
subprocess.run(command, shell=True)
logger.info("Waiting 30 seconds for Asterisk to make announcement...")
time.sleep(30)
# Get the duration of the alert_file
with contextlib.closing(wave.open(alert_file, 'r')) as f:
frames = f.getnframes()
rate = f.getframerate()
duration = math.ceil(frames / float(rate))
wait_time = duration + 5
logger.info("sayAlert: Waiting %s seconds for Asterisk to make announcement to avoid doubling alerts with tailmessage...", wait_time)
time.sleep(wait_time)
def sayAllClear():
@ -520,6 +535,10 @@ def buildTailmessage(alerts):
Args:
alerts (list): List of active weather alerts.
"""
# Extract only the alert names from the OrderedDict keys
alert_names = [alert[0] for alert in alerts.keys()]
if not alerts:
logger.debug("buildTailMessage: No alerts, creating silent tailmessage")
silence = AudioSegment.silent(duration=100)
@ -532,7 +551,7 @@ def buildTailmessage(alerts):
os.path.join(sounds_path, "ALERTS", "SWP_147.wav")
)
for alert in alerts:
for alert in alert_names:
if any(
fnmatch.fnmatch(alert, blocked_event)
for blocked_event in tailmessage_blocked_events
@ -719,6 +738,10 @@ def alertScript(alerts):
:param alerts: List of alerts to process
:type alerts: list[str]
"""
# Extract only the alert names from the OrderedDict keys
alert_names = [alert[0] for alert in alerts.keys()]
# Fetch AlertScript configuration from global_config
alertScript_config = config.get("AlertScript", {})
logger.debug("AlertScript configuration: %s", alertScript_config)
@ -739,7 +762,7 @@ def alertScript(alerts):
match_type = mapping.get("Match", "ANY").upper()
matched_alerts = []
for alert in alerts:
for alert in alert_names:
for trigger in triggers:
if fnmatch.fnmatch(alert, trigger):
logger.debug(
@ -762,14 +785,14 @@ def alertScript(alerts):
if mapping.get("Type") == "BASH":
logger.debug('Mapping type is "BASH"')
for cmd in commands:
logger.debug("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"')
for node in nodes:
for cmd in commands:
dtmf_cmd = 'asterisk -rx "rpt fun {} {}"'.format(node, cmd)
logger.debug("Executing DTMF command: %s", dtmf_cmd)
logger.info("AlertScript: Executing DTMF command: %s", dtmf_cmd)
subprocess.run(dtmf_cmd, shell=True)
@ -850,9 +873,14 @@ def change_and_log_CT_or_ID(
specified_alerts,
)
# Extract only the alert names from the OrderedDict keys
alert_names = [alert[0] for alert in alerts.keys()]
# Check if any alert matches specified_alerts
# Here we replace set intersection with a list comprehension
intersecting_alerts = [alert for alert in alerts if alert in specified_alerts]
intersecting_alerts = [
alert for alert in alert_names if alert in specified_alerts
]
if intersecting_alerts:
for alert in intersecting_alerts:
@ -944,10 +972,10 @@ def main():
if say_all_clear_enabled:
sayAllClear()
else:
if alertscript_enabled:
alertScript(alerts)
if say_alert_enabled:
sayAlert(alerts)
if alertscript_enabled:
alertScript(alerts)
# Check if tailmessage needs to be built
enable_tailmessage = config.get("Tailmessage", {}).get("Enable", False)

@ -74,9 +74,9 @@ Blocking:
Tailmessage:
# Configuration for the tail message functionality. Requires initial setup in RPT.CONF.
# Set 'Enable' to 'True' for enabling or 'False' for disabling.
Enable: false
Enable: true
# Specify an alternative path and filename for saving the tail message.
# Default is SkywarnPlus/SOUNDS/wx-tail.wav.
# Default is /tmp/SkywarnPlus/wx-tail.wav.
# TailmessagePath:
################################################################################################################################
@ -103,21 +103,36 @@ CourtesyTones:
# Define the alerts that trigger the weather courtesy tone.
# Use a case-sensitive list. One alert per line.
CTAlerts:
- Hurricane Force Wind Warning
- Severe Thunderstorm Warning
- Tropical Storm Warning
- Ashfall Warning
- Avalanche Warning
- Blizzard Warning
- Blowing Dust Warning
- Civil Danger Warning
- Civil Emergency Message
- Coastal Flood Warning
- Winter Storm Warning
- Thunderstorm Warning
- Extreme Wind Warning
- Storm Surge Warning
- Dust Storm Warning
- Avalanche Warning
- Ice Storm Warning
- Earthquake Warning
- Evacuation - Immediate
- Extreme Wind Warning
- Fire Warning
- Hazardous Materials Warning
- Hurricane Force Wind Warning
- Hurricane Warning
- Blizzard Warning
- Ice Storm Warning
- Law Enforcement Warning
- Local Area Emergency
- Nuclear Power Plant Warning
- Radiological Hazard Warning
- Severe Thunderstorm Warning
- Shelter In Place Warning
- Storm Surge Warning
- Tornado Warning
- Tornado Watch
- Tropical Storm Warning
- Tsunami Warning
- Typhoon Warning
- Volcano Warning
- Winter Storm Warning
################################################################################################################################
@ -138,33 +153,61 @@ IDChange:
# Define the alerts that trigger the weather ID.
# Use a case-sensitive list. One alert per line.
IDAlerts:
- Hurricane Force Wind Warning
- Severe Thunderstorm Warning
- Tropical Storm Warning
- Ashfall Warning
- Avalanche Warning
- Blizzard Warning
- Blowing Dust Warning
- Civil Danger Warning
- Civil Emergency Message
- Coastal Flood Warning
- Winter Storm Warning
- Thunderstorm Warning
- Extreme Wind Warning
- Storm Surge Warning
- Dust Storm Warning
- Avalanche Warning
- Ice Storm Warning
- Earthquake Warning
- Evacuation - Immediate
- Extreme Wind Warning
- Fire Warning
- Hazardous Materials Warning
- Hurricane Force Wind Warning
- Hurricane Warning
- Blizzard Warning
- Ice Storm Warning
- Law Enforcement Warning
- Local Area Emergency
- Nuclear Power Plant Warning
- Radiological Hazard Warning
- Severe Thunderstorm Warning
- Shelter In Place Warning
- Storm Surge Warning
- Tornado Warning
- Tornado Watch
- Tropical Storm Warning
- Tsunami Warning
- Typhoon Warning
- Volcano Warning
- Winter Storm Warning
################################################################################################################################
SkyDescribe:
Enable: false
APIKey:
# SkyDescribe is a feature that allows you to request a detailed description of a weather alert.
# VoiceRSS is a free service that SkyDescribe requires to function. You must obtain an API key from VoiceRSS.org.
# See VoiceRSS.ors/api/ for more information
# API Key for VoiceRSS.org
APIKey:
# VoiceRSS language code
Language: en-us
# VoiceRSS speech rate. -10 is slowest, 10 is fastest.
Speed: 0
# VoiceRSS voice profile. See VoiceRSS.org/api/ for more information.
Voice: John
# Maximum number of words to be spoken by SkyDescribe.
# CAUTION: Setting this value too high may cause SkyDescribe to exceed the timeout timer of your node.
# ~130 words is around 60 seconds at Speed: 0.
MaxWords: 150
################################################################################################################################
AlertScript:
# Completely enable/disable AlertScript
Enable: false
Enable: true
Mappings:
# Define the mapping of alerts to either DTMF commands or bash scripts here.
# Wildcards (*) can be used in the ALERTS for broader matches.
@ -223,11 +266,14 @@ AlertScript:
# - Tornado Watch
# Match: ANY
#
#
# This is an example entry that will automatically execute SkyDescribe and
# announce the full details of a Tornado Warning when it is detected.
- Type: BASH
Commands:
- 'echo "Tornado Warning detected!"'
- '/usr/local/bin/SkywarnPlus/SkyDescribe.py "Flood Warning"'
Triggers:
- Tornado Warning
- Flood Warning
################################################################################################################################

Loading…
Cancel
Save

Powered by TurnKey Linux.