v0.6.0 update

pull/140/head
Mason10198 2 years ago
parent 38f3a5c63c
commit 81af5d7e64

@ -1,3 +1,5 @@
# SkywarnPlus
![SkywarnPlus Logo](https://raw.githubusercontent.com/Mason10198/SkywarnPlus/main/Logo_SWP.svg)
![Release Version](https://img.shields.io/github/v/release/Mason10198/SkywarnPlus?label=Version&color=f15d24)
@ -8,49 +10,6 @@
**SkywarnPlus** is an advanced software solution tailored for Asterisk/app_rpt nodes. It is designed to provide important information about local government-issued alerts in the United States, thereby broadening the scope and functionality of your node. By intelligently integrating local alert data, SkywarnPlus brings a new layer of relevance and utility to your existing system. **SkywarnPlus** works with all major distributions, including AllstarLink, HAMVOIP, myGMRS, and more.
- [Installation](#installation)
- [**Instructional Video**](#instructional-video)
- [Written Instructions](#written-instructions)
- [TimeType Configuration](#timetype-configuration)
- [Tail Messages](#tail-messages)
- [Courtesy Tones](#courtesy-tones)
- [Configuration](#configuration)
- [CW / Voice IDs](#cw--voice-ids)
- [Configuration](#configuration-1)
- [Pushover Integration](#pushover-integration)
- [SkyControl](#skycontrol)
- [Usage](#usage)
- [Spoken Feedback](#spoken-feedback)
- [Mapping to DTMF Commands](#mapping-to-dtmf-commands)
- [AlertScript](#alertscript)
- [Understanding AlertScript](#understanding-alertscript)
- [Matching](#matching)
- [ClearCommands: Responding to Alert Clearance](#clearcommands-responding-to-alert-clearance)
- [The Power of YOU](#the-power-of-you)
- [SkyDescribe](#skydescribe)
- [Usage](#usage-1)
- [Integration with AlertScript](#integration-with-alertscript)
- [Mapping to DTMF commands](#mapping-to-dtmf-commands-1)
- [Customizing the Audio Files](#customizing-the-audio-files)
- [County Identifiers](#county-identifiers)
- [Automated Setup using `CountyIDGen.py`](#automated-setup-using-countyidgenpy)
- [Manual Setup](#manual-setup)
- [Testing](#testing)
- [Debugging](#debugging)
- [Maintenance and Bug Reporting](#maintenance-and-bug-reporting)
- [Contributing](#contributing)
- [Frequently Asked Questions](#frequently-asked-questions)
- [I just installed SkywarnPlus on my HAMVOIP node, why is it giving me errors?](#i-just-installed-skywarnplus-on-my-hamvoip-node-why-is-it-giving-me-errors)
- [Why do I see depreciation warnings when installing SWP on my HAMVOIP node?](#why-do-i-see-depreciation-warnings-when-installing-swp-on-my-hamvoip-node)
- [Can I change the crontab interval to something other than 60 seconds?](#can-i-change-the-crontab-interval-to-something-other-than-60-seconds)
- [What does "with multiples" mean?](#what-does-with-multiples-mean)
- [Why is SkywarnPlus saying the same thing every 60 seconds?](#why-is-skywarnplus-saying-the-same-thing-every-60-seconds)
- [I just installed SkywarnPlus, why don't I hear anything?](#i-just-installed-skywarnplus-why-dont-i-hear-anything)
- [There is an active alert in my area, but SkywarnPlus isn't doing anything. What gives?](#there-is-an-active-alert-in-my-area-but-skywarnplus-isnt-doing-anything-what-gives)
- [Why aren't my test alerts working?](#why-arent-my-test-alerts-working)
- [Can SkywarnPlus automatically read the full alert description?](#can-skywarnplus-automatically-read-the-full-alert-description)
- [License](#license)
## Key Features
- **Real-Time Alerts:** The software watches the new NWS v1.2 API for real-time alerts for user-defined areas.
@ -83,7 +42,7 @@
Whether you wish to auto-link to a Skywarn net during severe weather, program your node to control an external device like a siren during a tornado warning, or simply want to stay updated on changing weather conditions, SkywarnPlus offers a comprehensive, efficient, and customizable solution for your weather alert needs.
## Comprehensive Information
# Comprehensive Information
SkywarnPlus supports all 128 alert types included in the [NWS v1.2 API](https://www.weather.gov/documentation/services-web-api).
@ -135,11 +94,10 @@ SkywarnPlus supports all 128 alert types included in the [NWS v1.2 API](https://
# Installation
## **[Instructional Video](https://youtu.be/QyccjEZj20E)**
## **[Installation Video](https://youtu.be/QyccjEZj20E)**
[![Instructional Video](https://img.youtube.com/vi/QyccjEZj20E/maxresdefault.jpg)](https://youtu.be/QyccjEZj20E)
[![Installation Video](https://img.youtube.com/vi/QyccjEZj20E/maxresdefault.jpg)](https://youtu.be/QyccjEZj20E)
## Written Instructions
SkywarnPlus is recommended to be installed at the `/usr/local/bin/SkywarnPlus` location on both Debian and Arch (HAMVOIP) systems.
Follow the steps below to install:
@ -148,48 +106,14 @@ Follow the steps below to install:
Install the required dependencies using the following commands:
1. **Debian 11 and Older**
**Debian**
```bash
apt install unzip python3 python3-pip ffmpeg
pip3 install ruamel.yaml requests python-dateutil pydub
```
2. **Debian 12 and Newer**
Beginning around Debian 12 "Bookworm", installing Python packages via `pip` will have Debian throw a fit about package managers and externally managed virtual environments, etc:
```
error: externally-managed-environment
× This environment is externally managed
╰─> To install Python packages system-wide, try apt install
python3-xyz, where xyz is the package you are trying to
install.
If you wish to install a non-Debian-packaged Python package,
create a virtual environment using python3 -m venv path/to/venv.
Then use path/to/venv/bin/python and path/to/venv/bin/pip. Make
sure you have python3-full installed.
If you wish to install a non-Debian packaged Python application,
it may be easiest to use pipx install xyz, which will manage a
virtual environment for you. Make sure you have pipx installed.
See /usr/share/doc/python3.11/README.venv for more information.
note: If you believe this is a mistake, please contact your Python installation or OS distribution provider. You can override this, at the risk of breaking your Python installation or OS, by passing --break-system-packages.
hint: See PEP 668 for the detailed specification.
```
Use these commands instead when experiencing this on newer distros.
```bash
apt install unzip python3 python3-pip ffmpeg
apt install python3-ruamel.yaml python3-requests python3-dateutil python3-pydub
```
1. **Arch (HAMVOIP)**
**Arch (HAMVOIP)**
It is a good idea to first update your HAMVOIP system using **Option 1** in the HAMVOIP menu before installing the dependencies.
@ -201,7 +125,7 @@ Follow the steps below to install:
pip install ruamel.yaml==0.15.100
```
1. **Download SkywarnPlus**
2. **Download SkywarnPlus**
Download the latest release of SkywarnPlus from GitHub
@ -212,7 +136,7 @@ Follow the steps below to install:
rm SkywarnPlus.zip
```
2. **Configure Permissions**
3. **Configure Permissions**
The scripts must be made executable. Use the chmod command to change the file permissions:
@ -221,9 +145,7 @@ Follow the steps below to install:
chmod +x *.py
```
3. **Edit Configuration**
SkywarnPlus was designed with customization in mind, making it possible to fit nearly any usage scenario you can throw at it. However, this can make the configuration seem a bit daunting. Be patient, and when in doubt, read the documentation.
4. **Edit Configuration**
Edit the [config.yaml](config.yaml) file according to your needs. This is where you will enter your NWS codes, enable/disable specific functions, etc.
@ -233,7 +155,7 @@ Follow the steps below to install:
You can find your county codes in the [CountyCodes.md](CountyCodes.md) file included in this repository. Navigate to the file and look for your state and then your specific county to find the associated County Code you'll use in SkywarnPlus to poll for alerts.
**IMPORTANT**: YOU WILL MISS ALERTS IF YOU USE A **ZONE** CODE. DO NOT USE **ZONE** CODES UNLESS YOU KNOW WHAT YOU ARE DOING.
## **IMPORTANT**: YOU WILL MISS ALERTS IF YOU USE A **ZONE** CODE. DO NOT USE **ZONE** CODES UNLESS YOU KNOW WHAT YOU ARE DOING.
According to the official [NWS API documentation](https://www.weather.gov/documentation/services-web-api):
@ -263,7 +185,7 @@ Follow the steps below to install:
This means that if you use a County code, you will receive all alerts for both your County **AND** your Zone - but if you use a Zone code, you will **ONLY** receive alerts that cover the entire Zone, and none of the alerts specific to your County.
4. **Crontab Entry**
5. **Crontab Entry**
Add a crontab entry to call SkywarnPlus on an interval. Open your crontab file using the `crontab -e` command, and add the following line:
@ -271,37 +193,17 @@ Follow the steps below to install:
* * * * * /usr/local/bin/SkywarnPlus/SkywarnPlus.py
```
This command will execute SkywarnPlus (poll NWS API for data) every 60 seconds. For slower systems, or systems with several counties and/or advanced configurations, this may need to be increased.
This command will execute SkywarnPlus (poll NWS API for data) every minute.
**NOTE:**
# **NOTE:**
When SkywarnPlus runs for the first time after installation (and for the first time at each boot), **YOU WILL NOT HEAR ANY MESSAGES** until alerts are detected. This is by design. SkywarnPlus announces when alerts change from `none` to `some`, and it announces when alerts change from `some` to `none`. It will announce nothing if the status of alerts does not change (`none` to `none`).
If you want to test SkywarnPlus' operation after installation, please see the **Testing** section of this README.
# TimeType Configuration
This setting in SkywarnPlus determines the timing for issuing weather alerts. Users have the option to select between "onset" and "effective" time types, which influence the alerting strategy as follows:
# Tail Message
- **ONSET**
- With the ONSET setting, alerts are issued based on the anticipated start time of the weather event. This ensures that alerts are timely and relevant, focusing on imminent events. For instance, consider an Air Quality Alert issued due to a distant wildfire's smoke predicted to affect the area in three days time. While the alert might be issued early by the NWS, SkywarnPlus will only process the alert at the actual onset of the deteriorating air quality, avoiding premature notifications about conditions that are not yet affecting the area. Additionally, if Tailmessages are enabled, then using the ONSET setting prevents unnecessary repeated notifications of an event over an extended period of time.
- **EFFECTIVE**
- In contrast, the EFFECTIVE setting triggers SkywarnPlus to process alerts immediately upon their issuance from the NWS, regardless of the time until the subject matter is considered to be onset. This can result in alerts being announced well in advance of the actual event. Using the same Air Quality Alert scenario, the alert would be processed and announced as soon as it is issued, regardless of the smoke's actual arrival time, potentially leading to early warnings about conditions that are days away from materializing. Additionally, if Tailmessages are enabled, then the Air Quality Alert notifications would be continuously repeated for 3 days prior to the event actually occuring.
The default ONSET setting is recommended for ensuring that alerts are pertinent and actionable. It helps in maintaining the alert system's credibility by avoiding unnecessary alarms about conditions that are forecasted but not yet imminent, thereby aiding in better preparedness and response when the event actually occurs.
When in doubt, you can verity the exact data being provided by the NWS API, and whether an alert is currently EFFECTIVE or ONSET, by visiting the API endpoing in the following format:
```
https://api.weather.gov/alerts/active?zone=YOUR_COUNTY_CODE_HERE
```
**NOTE:**
Most weather websites and applications, including the NWS's own website, use the EFFECTIVE time when displaying "active" alerts. This often leads SkywarnPlus users to believe that their SkywarnPlus-enabled system is not functioning correctly when an alert is visible on the NWS website, but SkywarnPlus has not processed it yet. This discrepancy is due to the different alert processing times based on the chosen TimeType setting in SkywarnPlus. While other services might show alerts as soon as they become effective, SkywarnPlus, when set to ONSET, waits until the conditions are imminent. It's important for users to understand this distinction to accurately assess the functionality of their SkywarnPlus system.
# Tail Messages
SkywarnPlus can automatically create, manage, and remove a tail message whenever certain weather alerts are active to keep listeners informed throught the duration of active alerts. The configuration for this will be based on your `rpt.conf` file setup. Here's an example:
SkywarnPlus can automatically create, manage, and remove a tail message whenever certain weather alerts are active to keep listeners informed throught the duration of active alerts. The configuration for this is based on your `rpt.conf` file setup. Here's an example:
```ini
tailmessagetime = 600000
@ -311,46 +213,57 @@ tailmessagelist = /tmp/SkywarnPlus/wx-tail
# Courtesy Tones
SkywarnPlus has the ability to automatically change the node courtesy tone depending on the state of certain weather alerts. This feature can be configured via the `config.yaml` and `rpt.conf` files.
SkywarnPlus offers the capability to dynamically change node courtesy tones based on the current weather alert status. This feature enhances the responsiveness and informational value of the repeater system by providing auditory signals corresponding to specific weather conditions. Configuration is managed via the `config.yaml` and `rpt.conf` files, allowing for precise control over tone behavior.
## Configuration
## Configuration Overview
The setup process involves specifying your preferences in the `config.yaml` file and ensuring the `rpt.conf` file correctly references the managed courtesy tone files.
Here's an explanation of how it works using the default values in the `config.yaml` file:
### `config.yaml` Configuration
Within `config.yaml`, you can enable the feature, specify the directory for tone files, and define the tones for "normal" and "wx" (weather alert) modes. Here's an example configuration:
```yaml
CourtesyTones:
# Configuration for automatic CT changing. Requires initial setup in RPT.CONF.
# Enable/disable automatic courtesy tones.
Enable: false
Enable: true
# Specify an alternative directory where tone files are located.
# Default is SkywarnPlus/SOUNDS/TONES.
# Directory where tone files will be read from & stored to. Modify this path to match your setup.
# Default location is within the SkywarnPlus installation directory.
ToneDir: /usr/local/bin/SkywarnPlus/SOUNDS/TONES
# Define the sound files for courtesy tones.
# Define custom courtesy tones for use in different modes. This allows for dynamic response to weather alerts.
Tones:
# Audio file to feed Asterisk as ct1 in "normal" mode
CT1: Boop.ulaw
# Audio file to feed Asterisk as ct2 in "normal" mode
CT2: Beep.ulaw
# Audio file to feed Asterisk as ct1 AND ct2 in "wx" mode
WXCT: Stardust.ulaw
# The file rpt.conf is looking for as ct1
RptCT1: CT1.ulaw
# The file rpt.conf is looking for as ct2
RptCT2: CT2.ulaw
# Define each courtesy tone, and which files to use for that tone in Normal and WX mode.
ct1:
Normal: Boop.ulaw
WX: Stardust.ulaw
ct2:
Normal: Beep.ulaw
WX: Stardust.ulaw
ct3:
Normal: NBC.ulaw
WX: SatPass.ulaw
ct4:
Normal: BlastOff.ulaw
WX: Target.ulaw
ct5:
Normal: BumbleBee.ulaw
WX: XPError.ulaw
ct6:
Normal: Comet.ulaw
WX: Waterdrop.ulaw
```
In this configuration, if none of the alerts defined in the CTAlerts list are active (i.e., the system is in "NORMAL" mode), SkywarnPlus will replace the files `CT1.ulaw` and `CT2.ulaw` with duplicates of `Boop.ulaw` and `Beep.ulaw` respectively.
However, if any alerts in the CTAlerts list are active (i.e., the system is in "WX" mode), SkywarnPlus will replace both `CT1.ulaw` and `CT2.ulaw` with duplicates of `Stardust.ulaw`.
### `rpt.conf` Configuration
If you want `CT1.ulaw` to be your "local" traffic tone and `CT2.ulaw` to be your "link" traffic tone, then the following modifications are required in your `rpt.conf` file:
Ensure `rpt.conf` is set up to reference the courtesy tone files that SkywarnPlus manages. The configuration should match the defined tones in `config.yaml`. Example:
```ini
[NODENUMBER]
@ -358,16 +271,21 @@ unlinkedct = ct1
remotect = ct1
linkunkeyct = ct2
[telemetry]
ct1 = /usr/local/bin/SkywarnPlus/SOUNDS/TONES/CT1
ct2 = /usr/local/bin/SkywarnPlus/SOUNDS/TONES/CT2
remotetx = /usr/local/bin/SkywarnPlus/SOUNDS/TONES/CT1
ct1 = /usr/local/bin/SkywarnPlus/SOUNDS/TONES/ct1
ct2 = /usr/local/bin/SkywarnPlus/SOUNDS/TONES/ct2
ct1 = /usr/local/bin/SkywarnPlus/SOUNDS/TONES/ct3
ct2 = /usr/local/bin/SkywarnPlus/SOUNDS/TONES/ct4
remotetx = /usr/local/bin/SkywarnPlus/SOUNDS/TONES/ct1
remotemon = /usr/local/bin/SkywarnPlus/SOUNDS/TONES/ct1
```
With this setup, Asterisk will always use `CT1.ulaw` for "local" traffic, and `CT2.ulaw` for "link" traffic. SkywarnPlus essentially changes the contents of the `CT1.ulaw` and `CT2.ulaw` files while Asterisk "isn't looking".
### Dynamic Tone Switching
Please note the filenames are case-sensitive, so be sure they match exactly between `rpt.conf` and `config.yaml`.
When enabled, SkywarnPlus will automatically switch between "normal" and "wx" mode tones based on the active weather alerts defined in the `CTAlerts` section of `config.yaml`. This change enhances situational awareness through auditory cues.
After initially setting up automatic courtesy tones, the audio files will not refresh until the next time the alert status changes. To refresh immediately, run `/usr/local/bin/SkywarnPlys/SkyControl.py changect normal` to force the CTs to "normal" mode.
### Consistent Filenames
Ensure that filenames and case sensitivity are consistent across `config.yaml` and `rpt.conf` to ensure seamless operation.
# CW / Voice IDs
@ -466,7 +384,7 @@ And to toggle it, you would use:
/usr/local/bin/SkywarnPlus/SkyControl.py enable toggle
```
**NOTE:**
## **NOTE:**
Running the `Enable` command after installing SkywarnPlus is not necessary. The enable flag is already set to `true` in the `config.yaml` file by default, and all you need to do for SkywarnPlus to operate is add it to the `crontab`.
@ -688,7 +606,7 @@ For added flexibility, `SkyDescribe.py` can also be linked to DTMF commands, all
849 = cmd,/usr/local/bin/SkywarnPlus/SkyDescribe.py 9 ; SkyDescribe the 9th alert
```
**NOTE:**
## **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**.
@ -758,24 +676,13 @@ SkywarnPlus provides the ability to inject predefined alerts, bypassing the call
To enable this option, modify the following settings in the `[DEV]` section of your `config.yaml` file:
```yaml
# Enable test alert injection instead of calling the NWS API by setting 'INJECT' to 'True'.
INJECT: false
# List the test alerts to inject. Alert titles are case sensitive.
# Optionally specify the CountyCodes and/or EndTime for each alert.
# CountyCodes used here must be defined at the top of this configuration file.
# Example:
# INJECTALERTS:
# - Title: "Tornado Warning"
# CountyCodes: ["ARC119", "ARC120"]
# - Title: "Tornado Watch"
# CountyCodes: ["ARC125"]
# EndTime: "2023-08-01T12:00:00Z"
# - Title: "Severe Thunderstorm Warning"
INJECTALERTS:
- Title: "Tornado Warning"
- Title: "Tornado Watch"
- Title: "Severe Thunderstorm Warning"
# Enable test alert injection instead of calling the NWS API by setting 'INJECT' to 'True'.
INJECT: false
# List the test alerts to inject. Use a case-sensitive list. One alert per line for better readability.
INJECTALERTS:
- Tornado Warning
- Tornado Watch
- Severe Thunderstorm Warning
```
# Debugging
@ -827,38 +734,6 @@ If the spare time I put into the development of SkywarnPlus has helped you, plea
<p align="center"><a href="https://www.paypal.com/donate/?business=93AJFB9BAVSJL&no_recurring=0&item_name=Thank+you+so+much+for+your+support%21+I+put+a+lot+of+my+spare+time+into+this%2C+and+I+sincerely+appreciate+YOU%21&currency_code=USD"><img src="https://raw.githubusercontent.com/stefan-niedermann/paypal-donate-button/master/paypal-donate-button.png" width=300px alt="Donate with PayPal"/></a></p>
# Frequently Asked Questions
### I just installed SkywarnPlus on my HAMVOIP node, why is it giving me errors?
HAMVOIP uses a very outdated version of Python which can cause some issues that ASL users do not experience. Carefully follow the installation inctructions line-by-line (do not copy/paste all commands at once) and try again.
### Why do I see depreciation warnings when installing SWP on my HAMVOIP node?
HAMVOIP uses a very outdated version of Python, and Python will display warnings asking you to update it. Unfortunately, Python cannot be upgraded on HAMVOIP and these warnings must be ignored.
### Can I change the crontab interval to something other than 60 seconds?
Yes! You can run SkywarnPlus as frequently or infrequently as you wish. Be aware, whatever you set the interval to (X), there will be a delay of "up to" X minutes between the time an alert is issued by the NWS, and the time that SWP announces it.
### What does "with multiples" mean?
The "multiples" flag informs the listener that there is more than one unique instance of the given alert type in the county/counties you defined in the configuration. For example, a config file with 2x counties defined, and a unique Tornado Warning in each county.
### Why is SkywarnPlus saying the same thing every 60 seconds?
You probably have the `CLEANSLATE` developer option enabled in the `config.yaml` file by accident.
### I just installed SkywarnPlus, why don't I hear anything?
Assuming you installed it correctly, SkywarnPlus will not do anything until it detects alerts provided by the NWS.
### There is an active alert in my area, but SkywarnPlus isn't doing anything. What gives?
It is very likely that the alert is not technically active yet in your area, and SkywarnPlus is holding off on announcing that alert until it is imminent. Please see the [TimeType Configuration](#timetype-configuration) section for more information. When in doubt, you can verity the exact data being provided by the NWS API, and whether an alert is currently EFFECTIVE or ONSET, by visiting the API endpoing in the following format:
```
https://api.weather.gov/alerts/active?zone=YOUR_COUNTY_CODE_HERE
```
### Why aren't my test alerts working?
Make sure you're injecting alerts with the correct format, shown in the [Testing](#testing) section.
### Can SkywarnPlus automatically read the full alert description?
Yes! You can use [AlertScript](#alertscript) to automcatially trigger [SkyDescribe](#skydescribe) whenever specific alerts are detected.
# License
SkywarnPlus is open-sourced software licensed under the [GPL-3.0 license](LICENSE).

@ -33,41 +33,61 @@ from ruamel.yaml import YAML
yaml = YAML()
# Define a function to change the CT
def changeCT(ct):
def changeCT(ct_mode):
"""
Changes all courtesy tones to the specified mode ('normal' or 'wx').
This function ensures that the case of the keys in the config.yaml is correctly handled,
dynamically selecting and applying tone configurations based on the mode.
:param ct_mode: The operational mode to switch to ('normal' or 'wx').
"""
ct_mode_lower = ct_mode.lower() # Convert the mode to lowercase for comparison
if ct_mode_lower not in ["normal", "wx"]:
print("Invalid CT mode. Please provide either 'wx' or 'normal'.")
sys.exit(1)
mode_key = (
"Normal" if ct_mode_lower == "normal" else "WX"
) # Convert to the case used in config.yaml
tone_dir = config["CourtesyTones"].get(
"ToneDir", os.path.join(str(SCRIPT_DIR), "SOUNDS/TONES")
"ToneDir", "/usr/local/bin/SkywarnPlus/SOUNDS/TONES"
)
ct1 = config["CourtesyTones"]["Tones"]["CT1"]
ct2 = config["CourtesyTones"]["Tones"]["CT2"]
wx_ct = config["CourtesyTones"]["Tones"]["WXCT"]
rpt_ct1 = config["CourtesyTones"]["Tones"]["RptCT1"]
rpt_ct2 = config["CourtesyTones"]["Tones"]["RptCT2"]
if ct == "normal":
src_file = os.path.join(tone_dir, ct1)
dest_file = os.path.join(tone_dir, rpt_ct1)
shutil.copyfile(src_file, dest_file)
src_file = os.path.join(tone_dir, ct2)
dest_file = os.path.join(tone_dir, rpt_ct2)
shutil.copyfile(src_file, dest_file)
return True # Indicate that CT was changed to normal
elif ct == "wx":
src_file = os.path.join(tone_dir, wx_ct)
dest_file = os.path.join(tone_dir, rpt_ct1)
shutil.copyfile(src_file, dest_file)
tones_config = config["CourtesyTones"]["Tones"]
changes_made = False
for ct_key, settings in tones_config.items():
# Normalize the ct_key to lowercase to ensure consistent access
target_tone = settings.get(
mode_key
) # Access settings using the corrected mode key
if not target_tone:
print("No tone configured for {} mode in {}".format(ct_mode_lower, ct_key))
continue
src_file = os.path.join(tone_dir, target_tone)
dest_file = os.path.join(tone_dir, "{}.ulaw".format(ct_key))
# Check if the source file exists and perform the file copy operation
if os.path.exists(src_file):
shutil.copyfile(src_file, dest_file)
print(
"Updated {} to {} mode with tone {}".format(
ct_key, ct_mode, target_tone
)
)
changes_made = True
else:
print("Source tone file does not exist: {}".format(src_file))
src_file = os.path.join(tone_dir, wx_ct)
dest_file = os.path.join(tone_dir, rpt_ct2)
shutil.copyfile(src_file, dest_file)
return False # Indicate that CT was changed to wx
if changes_made:
print("All courtesy tones updated to {} mode.".format(ct_mode_lower))
else:
print("Invalid CT value. Please provide either 'wx' or 'normal'.")
sys.exit(1)
print("No changes made to courtesy tones.")
return changes_made
# Define a function to change the ID
def changeID(id):
id_dir = config["IDChange"].get("IDDir", os.path.join(str(SCRIPT_DIR), "ID"))
normal_id = config["IDChange"]["IDs"]["NormalID"]

@ -41,6 +41,7 @@ import contextlib
import math
import sys
import itertools
import argparse
from datetime import datetime, timezone, timedelta
from dateutil import parser
from pydub import AudioSegment
@ -55,6 +56,13 @@ BASE_DIR = os.path.dirname(os.path.realpath(__file__))
CONFIG_PATH = os.path.join(BASE_DIR, "config.yaml")
COUNTY_CODES_PATH = os.path.join(BASE_DIR, "CountyCodes.md")
# Setup argparser
parser = argparse.ArgumentParser()
parser.add_argument(
"--inject", action="store_true", help="Enable inject directly via flag"
)
args = parser.parse_args()
# Open and read configuration file
with open(CONFIG_PATH, "r") as config_file:
config = yaml.load(config_file)
@ -404,7 +412,7 @@ def get_alerts(countyCodes):
LOGGER.debug("getAlerts: Current time: %s", current_time)
# Handle alert injection for development/testing purposes
if config.get("DEV", {}).get("INJECT", False):
if config.get("DEV", {}).get("INJECT", False) or args.inject:
LOGGER.debug("getAlerts: DEV Alert Injection Enabled")
injected_alerts = config["DEV"].get("INJECTALERTS", [])
LOGGER.debug("getAlerts: Injecting alerts: %s", injected_alerts)
@ -462,9 +470,9 @@ def get_alerts(countyCodes):
}
)
alerts[
alert_title
] = county_data # Add the list of dictionaries to the alert
alerts[alert_title] = (
county_data # Add the list of dictionaries to the alert
)
# If injected alerts are used, we return them here and don't proceed with the function.
return sort_alerts(alerts)
@ -1109,121 +1117,6 @@ def build_tailmessage(alerts):
converted_combined_sound.export(TAILMESSAGE_FILE, format="wav")
def change_ct(ct):
"""
Change the current Courtesy Tone (CT) to the one specified.
This function first checks if the specified CT is already in use. If so, it does not make any changes.
If the CT needs to be changed, it replaces the current CT files with the new ones and updates the state file.
"""
state = load_state()
current_ct = state["ct"]
ct1 = config["CourtesyTones"]["Tones"]["CT1"]
ct2 = config["CourtesyTones"]["Tones"]["CT2"]
wx_ct = config["CourtesyTones"]["Tones"]["WXCT"]
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)
if not ct:
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)
if ct == current_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)
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)
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)
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)
shutil.copyfile(src_file, dest_file)
state["ct"] = ct
save_state(state)
return True
def change_id(id):
"""
Change the current Identifier (ID) to the one specified.
This function first checks if the specified ID is already in use. If so, it does not make any changes.
If the ID needs to be changed, it replaces the current ID files with the new ones and updates the state file.
"""
state = load_state()
current_id = state["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)
if not id:
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)
if id == current_id:
LOGGER.debug("changeID: ID is already %s, no changes made.", id)
return False
if id == "NORMAL":
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)
shutil.copyfile(src_file, dest_file)
else:
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)
shutil.copyfile(src_file, dest_file)
state["id"] = id
save_state(state)
return True
def alert_script(alerts):
"""
This function reads a list of alerts, then performs actions based
@ -1417,20 +1310,164 @@ def change_ct_id_helper(
LOGGER.debug("%s auto change is not enabled", alert_type)
def change_ct(mode):
"""
Dynamically changes courtesy tones based on the specified operational mode ('NORMAL' or 'WX') and the detailed configuration provided.
This function extends flexibility by supporting multiple courtesy tones, each with configurations for normal and wx modes.
:param mode: The operational mode, either 'NORMAL' or 'WX'.
:return: True if any changes were made, False otherwise.
"""
mode = mode.lower() # Normalize the mode to lowercase
state = load_state() # Load the current state
tone_dir = config["CourtesyTones"]["ToneDir"]
tones_config = config["CourtesyTones"]["Tones"]
LOGGER.debug("change_ct: Starting courtesy tone change to mode: %s", mode)
LOGGER.debug("change_ct: Courtesy tone directory: %s", tone_dir)
changed = False
for ct_key, tone_settings in tones_config.items():
# Normalize keys in tone_settings to lowercase for case-insensitive comparison
tone_settings_lower = {k.lower(): v for k, v in tone_settings.items()}
target_tone = tone_settings_lower.get(mode) # Access using normalized mode
if not target_tone:
LOGGER.error(
"change_ct: No target tone specified for %s in %s mode", ct_key, mode
)
continue
src_file = os.path.join(tone_dir, target_tone)
dest_file = os.path.join(tone_dir, "{}.ulaw".format(ct_key))
if not os.path.exists(src_file):
LOGGER.error("change_ct: Source tone file does not exist: %s", src_file)
continue
try:
shutil.copyfile(src_file, dest_file)
LOGGER.info(
"ChangeCT: Updated %s to %s mode with tone %s",
ct_key,
mode,
target_tone,
)
changed = True
except Exception as e:
LOGGER.error("ChangeCT: Failed to update %s: %s", ct_key, str(e))
if changed:
LOGGER.debug("ChangeCT: Changes made, updating state to %s mode", mode)
state["ct"] = mode # Update the state with the new mode
save_state(state)
else:
LOGGER.debug(
"ChangeCT: No changes made, courtesy tones already set for %s mode", mode
)
return changed
def change_id(id):
"""
Change the current Identifier (ID) to the one specified.
This function first checks if the specified ID is already in use. If so, it does not make any changes.
If the ID needs to be changed, it replaces the current ID files with the new ones and updates the state file.
"""
try:
state = load_state()
except Exception as e:
LOGGER.error("changeID: Failed to load state: %s", e)
return False
if not state or "id" not in state:
LOGGER.error("changeID: State is invalid or missing 'id'")
return False
current_id = state["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)
if not id:
LOGGER.error("changeID: called with no ID specified")
return False
LOGGER.debug("changeID: Current ID - %s", current_id)
if id == current_id:
LOGGER.debug("changeID: ID is already %s, no changes made.", id)
return False
src_file = ""
if id == "NORMAL":
src_file = os.path.join(id_dir, normal_id)
else:
src_file = os.path.join(id_dir, wx_id)
dest_file = os.path.join(id_dir, rpt_id)
if not os.path.exists(src_file):
LOGGER.error("changeID: Source file does not exist: %s", src_file)
return False
try:
LOGGER.info("Changing to %s ID", id)
LOGGER.debug("changeID: Copying %s to %s", src_file, dest_file)
shutil.copyfile(src_file, dest_file)
except Exception as e:
LOGGER.error(
"changeID: Failed to copy file from %s to %s: %s", src_file, dest_file, e
)
return False
try:
state["id"] = id
save_state(state)
except Exception as e:
LOGGER.error("changeID: Failed to save state: %s", e)
return False
return True
def supermon_back_compat(alerts):
"""
Write alerts to a file for backward compatibility with supermon.
"""
try:
# Ensure the target directory exists for /tmp/AUTOSKY
os.makedirs("/tmp/AUTOSKY", exist_ok=True)
# Get alert titles (without severity levels)
alert_titles = list(alerts.keys())
# Ensure the target directory exists
os.makedirs("/tmp/AUTOSKY", exist_ok=True)
# Write alert titles to a file, with each title on a new line
with open("/tmp/AUTOSKY/warnings.txt", "w") as file:
file.write("<br>".join(alert_titles))
# Get alert titles (without severity levels)
alert_titles = list(alerts.keys())
except Exception as e:
print("An error occurred while writing to /tmp/AUTOSKY: {}".format(str(e)))
# Write alert titles to a file, with each title on a new line
with open("/tmp/AUTOSKY/warnings.txt", "w") as file:
file.write("<br>".join(alert_titles))
try:
# Ensure the target directory exists for /var/www/html/AUTOSKY
os.makedirs("/var/www/html/AUTOSKY", exist_ok=True)
# Also write to other path sometimes used by Supermon
with open("/var/www/html/AUTOSKY/warnings.txt", "w") as file:
file.write("<br>".join(alert_titles))
except Exception as e:
print(
"An error occurred while writing to /var/www/html/AUTOSKY: {}".format(
str(e)
)
)
def detect_county_changes(old_alerts, new_alerts):

@ -153,32 +153,41 @@ Tailmessage:
CourtesyTones:
# Configuration for automatic CT changing. Requires initial setup in RPT.CONF.
# Enable/disable automatic courtesy tones.
Enable: false
Enable: true
# Specify an alternative directory where tone files are located.
# Default is SkywarnPlus/SOUNDS/TONES.
# Directory where tone files will be read from & stored to. Modify this path to match your setup.
# Default location is within the SkywarnPlus installation directory.
ToneDir: /usr/local/bin/SkywarnPlus/SOUNDS/TONES
# Define the sound files for courtesy tones.
# Define custom courtesy tones for use in different modes. This allows for dynamic response to weather alerts.
Tones:
# Audio file to feed Asterisk as ct1 in "normal" mode
CT1: Boop.ulaw
# Audio file to feed Asterisk as ct2 in "normal" mode
CT2: Beep.ulaw
# Audio file to feed Asterisk as ct1 AND ct2 in "wx" mode
WXCT: Stardust.ulaw
# The file rpt.conf is looking for as ct1
RptCT1: CT1.ulaw
# The file rpt.conf is looking for as ct2
RptCT2: CT2.ulaw
# Define the alerts that trigger the "wx" courtesy tone.
# Define each courtesy tone, and which files to use for that tone in Normal and WX mode.
ct1:
Normal: Boop.ulaw
WX: Stardust.ulaw
ct2:
Normal: Beep.ulaw
WX: Stardust.ulaw
# ct3:
# Normal: NBC.ulaw
# WX: SatPass.ulaw
# ct4:
# Normal: BlastOff.ulaw
# WX: Target.ulaw
# ct5:
# Normal: BumbleBee.ulaw
# WX: XPError.ulaw
# ct6:
# Normal: Comet.ulaw
# WX: Waterdrop.ulaw
# Define the alerts that trigger the "WX" courtesy tone.
# Use a case-sensitive list. One alert per line.
CTAlerts:
- Ashfall Warning
@ -435,4 +444,4 @@ DEV:
INJECTALERTS:
- Title: "Tornado Warning"
- Title: "Tornado Watch"
- Title: "Severe Thunderstorm Warning"
- Title: "Severe Thunderstorm Warning"
Loading…
Cancel
Save

Powered by TurnKey Linux.