parent
6185c026c5
commit
e8b7fb92fb
@ -0,0 +1,84 @@
|
||||
#!/bin/bash
|
||||
|
||||
# CONTROL.sh
|
||||
# A Control Script for SkywarnPlus
|
||||
#
|
||||
# This script allows you to change the value of specific keys in the SkywarnPlus config.ini file.
|
||||
# It's designed to enable or disable certain features of SkywarnPlus from the command line.
|
||||
# It is case-insensitive, accepting both upper and lower case parameters.
|
||||
#
|
||||
# Usage: ./CONTROL.sh <key> <value>
|
||||
# Example: ./CONTROL.sh sayalert false
|
||||
# This will set 'SayAlert' to 'False' in the config.ini file.
|
||||
#
|
||||
# Supported keys:
|
||||
# - enable: Enable or disable SkywarnPlus entirely. (Section: SKYWARNPLUS)
|
||||
# - sayalert: Enable or disable instant alerting when weather alerts change. (Section: Alerting)
|
||||
# - sayallclear: Enable or disable instant alerting when weather alerts are cleared. (Section: Alerting)
|
||||
# - tailmessage: Enable or disable building of tail message. (Section: Tailmessage)
|
||||
# - courtesytone: Enable or disable automatic courtesy tones. (Section: CourtesyTones)
|
||||
#
|
||||
# All changes will be made in the config.ini file located in the same directory as the script.
|
||||
|
||||
# First, we need to check if the correct number of arguments are passed
|
||||
if [ "$#" -ne 2 ]; then
|
||||
echo "Incorrect number of arguments. Please provide the key and the new value."
|
||||
echo "Usage: $0 <key> <value>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get the directory of the script
|
||||
SCRIPT_DIR=$(dirname $(readlink -f $0))
|
||||
|
||||
# Convert the input key into lowercase
|
||||
KEY=$(echo "$1" | tr '[:upper:]' '[:lower:]')
|
||||
|
||||
# Convert the first character of the value to uppercase
|
||||
VALUE=$(echo "$2" | awk '{for(i=1;i<=NF;i++)sub(/./,toupper(substr($i,1,1)),$i)}1')
|
||||
|
||||
# Make sure the provided value is either 'True' or 'False'
|
||||
if [[ "${VALUE^^}" != "TRUE" && "${VALUE^^}" != "FALSE" ]]; then
|
||||
echo "Invalid value. Please provide either 'true' or 'false'."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Define the command-line arguments and their corresponding keys in the configuration file
|
||||
declare -A ARGUMENTS=( ["enable"]="Enable" ["sayalert"]="SayAlert" ["sayallclear"]="SayAllClear" ["tailmessage"]="Enable" ["courtesytone"]="Enable")
|
||||
|
||||
# Define the sections in the configuration file that each key belongs to
|
||||
declare -A SECTIONS=( ["enable"]="SKYWARNPLUS" ["sayalert"]="Alerting" ["sayallclear"]="Alerting" ["tailmessage"]="Tailmessage" ["courtesytone"]="CourtesyTones")
|
||||
|
||||
# Define the audio files associated with each key
|
||||
declare -A AUDIO_FILES_ENABLED=( ["enable"]="SWP85.wav" ["sayalert"]="SWP87.wav" ["sayallclear"]="SWP89.wav" ["tailmessage"]="SWP91.wav" ["courtesytone"]="SWP93.wav")
|
||||
|
||||
declare -A AUDIO_FILES_DISABLED=( ["enable"]="SWP86.wav" ["sayalert"]="SWP88.wav" ["sayallclear"]="SWP90.wav" ["tailmessage"]="SWP92.wav" ["courtesytone"]="SWP94.wav")
|
||||
|
||||
# Read the node number and path to SOUNDS directory from the config.ini
|
||||
NODES=$(awk -F " = " '/^Nodes/ {print $2}' "${SCRIPT_DIR}/config.ini" | tr -d ' ' | tr ',' '\n')
|
||||
|
||||
# Check if the input key is valid
|
||||
if [[ ${ARGUMENTS[$KEY]+_} ]]; then
|
||||
# Get the corresponding key in the configuration file
|
||||
CONFIG_KEY=${ARGUMENTS[$KEY]}
|
||||
|
||||
# Get the section that the key belongs to
|
||||
SECTION=${SECTIONS[$KEY]}
|
||||
|
||||
# Update the value of the key in the configuration file
|
||||
sed -i "/^\[${SECTION}\]/,/^\[/{s/^${CONFIG_KEY} = .*/${CONFIG_KEY} = ${VALUE}/}" "${SCRIPT_DIR}/config.ini"
|
||||
|
||||
# Get the correct audio file based on the new value
|
||||
if [ "$VALUE" = "True" ]; then
|
||||
AUDIO_FILE=${AUDIO_FILES_ENABLED[$KEY]}
|
||||
else
|
||||
AUDIO_FILE=${AUDIO_FILES_DISABLED[$KEY]}
|
||||
fi
|
||||
|
||||
# Play the corresponding audio message on all nodes
|
||||
for NODE in $NODES; do
|
||||
/usr/sbin/asterisk -rx "rpt localplay ${NODE} ${SCRIPT_DIR}/SOUNDS/ALERTS/${AUDIO_FILE%.*}"
|
||||
done
|
||||
else
|
||||
echo "The provided key does not match any configurable item."
|
||||
exit 1
|
||||
fi
|
||||
@ -0,0 +1,244 @@
|
||||
# SkywarnPlus
|
||||
|
||||
SkywarnPlus is an optimized, powerful weather alert system designed for Asterisk/app_rpt repeater controller systems such as [AllStarLink](https://allstarlink.org/) and ~~[HAMVOIP](https://hamvoip.org/)~~ (in progress). It's written in Python and utilizes the new [NWS CAP v1.2 JSON API](https://www.weather.gov/documentation/services-web-api). SkywarnPlus is optimized to be resource-efficient and offers customization options to suit various user needs.
|
||||
|
||||
## Features
|
||||
|
||||
* **Human Speech Alerts**: Provides a library of recorded human speech for clearer, more understandable alerts.
|
||||
* **Performance**: Designed for minimal impact on internet bandwidth and storage, reducing unnecessary I/O operations.
|
||||
* **Alert Coverage**: Allows specifying multiple counties or zones for alerts, ensuring broad coverage.
|
||||
* **Alert Filtering**: Provides advanced options to block or filter alerts using regular expressions and wildcards.
|
||||
* **Courtesy Tone Changes**: Changes repeater courtesy tones based on active alerts.
|
||||
* **Duplicate Alert Removal**: Ensures you receive unique and relevant alerts by automatically removing duplicates.
|
||||
* **Selective Broadcasting**: Broadcasts alerts on weather conditions' onset or dissipation.
|
||||
* **Tailmessage Management**: Provides unobtrusive alerting if alert broadcasting is disabled.
|
||||
* **Pushover Integration**: Sends alerts and debug messages directly to your phone.
|
||||
* **Multiple Local Nodes**: Supports alert distribution to as many local node numbers as desired.
|
||||
* **Developer Options**: Provides a testing environment to inject manually defined alerts for testing how the system functions.
|
||||
|
||||
## How It Works
|
||||
|
||||
SkywarnPlus is a Python-based weather alert system for Asterisk/app_rpt repeater controller systems, leveraging the National Weather Service's (NWS) CAP v1.2 JSON API. The system follows several key steps to deliver timely and accurate weather alerts:
|
||||
|
||||
1. **Data Fetching**: The system performs regular API calls to the NWS CAP v1.2 API, which provides comprehensive, real-time data on the latest weather conditions and alerts. The frequency of these calls can be adjusted according to user needs.
|
||||
|
||||
2. **Data Parsing**: Upon receiving the API response, SkywarnPlus parses the JSON data to extract the information pertinent to weather alerts. This involves reading the structured JSON data and converting it into an internal format for further processing.
|
||||
|
||||
3. **Data Filtering**: The extracted data is then filtered based on user-defined criteria set in the configuration file. This includes narrowing down the information to specific counties or zones of interest, as well as excluding certain types of alerts. The filtering mechanism supports regular expressions and wildcards for more sophisticated filtering rules.
|
||||
|
||||
4. **Alert Management**: SkywarnPlus manages the filtered alerts intelligently, ensuring that each alert is unique and relevant. Duplicate alerts are automatically removed from the pool of active alerts to prevent repetition and alert fatigue.
|
||||
|
||||
5. **Alert Broadcasting**: The system then broadcasts the alerts according to user-defined settings. You can customize these settings to broadcast alerts when new weather conditions are detected or when existing conditions dissipate. This ensures timely communication of weather changes.
|
||||
|
||||
6. **Tailmessage and Courtesy Tones**: In addition to broadcasting alerts, SkywarnPlus also automatically updates tailmessages and changes the repeater courtesy tones when specific alerts are active. These changes add a level of customization and context-awareness to the alert system and can be tailored to individual preferences.
|
||||
|
||||
7. **Pushover Integration**: SkywarnPlus integrates with Pushover, a mobile notification service, to send alerts and debug messages directly to your phone. This provides a direct and immediate communication channel, keeping you constantly updated on the latest weather conditions.
|
||||
|
||||
8. **Real Human Speech**: To enhance clarity and improve user experience, SkywarnPlus uses a library of real female human speech recordings for alerts. This creates a more natural listening experience compared to synthetic speech and aids in clear communication of alert messages.
|
||||
|
||||
9. **Maintenance and Resource Management**: Designed with efficiency in mind, SkywarnPlus minimizes its impact on internet bandwidth and physical storage. The system conducts its operations mindful of resource usage, making it particularly suitable for devices with limited resources, such as Raspberry Pi.
|
||||
|
||||
This combination of steps ensures SkywarnPlus provides reliable, timely, and accurate weather alerts, while respecting your system's resources and providing extensive customization options.
|
||||
|
||||
# Installation
|
||||
|
||||
SkywarnPlus is recommended to be installed at the `/usr/local/bin/SkywarnPlus` location on a Debian (AllStarLink) machine. **HAMVOIP is not yet supported due to Python3.5 issues, but is actively being worked on.**
|
||||
|
||||
Follow the steps below to install:
|
||||
|
||||
1. **Dependencies**
|
||||
|
||||
Install the required dependencies using the following commands:
|
||||
|
||||
**Debian**
|
||||
```bash
|
||||
apt install python3 python3-pip ffmpeg
|
||||
pip3 install requests python-dateutil pydub
|
||||
```
|
||||
<!--
|
||||
**Arch**
|
||||
```bash
|
||||
sudo pacman -S python python-pip ffmpeg
|
||||
pip install requests python-dateutil pydub
|
||||
```
|
||||
-->
|
||||
2. **Clone the Repository**
|
||||
|
||||
Clone the SkywarnPlus repository from GitHub to the `/usr/local/bin` directory:
|
||||
|
||||
```bash
|
||||
cd /usr/local/bin
|
||||
git clone https://github.com/mason10198/SkywarnPlus.git
|
||||
```
|
||||
3. **Configure CONTROL.sh Permissions**
|
||||
|
||||
The CONTROL.sh script must be made executable. Use the chmod command to change the file permissions:
|
||||
|
||||
```bash
|
||||
sudo chmod +x /usr/local/bin/SkywarnPlus/CONTROL.sh
|
||||
```
|
||||
|
||||
4. **Edit Configuration**
|
||||
|
||||
Edit the configuration file to suit your system:
|
||||
|
||||
```bash
|
||||
sudo nano SkywarnPlus/config.ini
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```bash
|
||||
* * * * * /usr/bin/python3 /usr/local/bin/SkywarnPlus/SkywarnPlus.py
|
||||
```
|
||||
|
||||
This command will execute SkywarnPlus every minute.
|
||||
|
||||
# Configuration
|
||||
|
||||
Update parameters in the [config.ini](config.ini) file according to your preferences.
|
||||
|
||||
Remember you can also use CONTROL.sh to conveniently change specific key-value pairs in the config file from the command line. For example: `./CONTROL.sh sayalert false` would set 'SayAlert' to 'False'.
|
||||
|
||||
## Tailmessage and Courtesy Tones
|
||||
|
||||
SkywarnPlus offers functionalities such as Tailmessage management and Automatic Courtesy Tones, which require specific configurations in the `rpt.conf` file.
|
||||
|
||||
### Tailmessage
|
||||
|
||||
Tailmessage functionality requires the `rpt.conf` to be properly set up. Here's an example:
|
||||
|
||||
```ini
|
||||
tailmessagetime = 600000
|
||||
tailsquashedtime = 30000
|
||||
tailmessagelist = /usr/local/bin/SkywarnPlus/SOUNDS/wx-tail
|
||||
```
|
||||
|
||||
### Automatic Courtesy Tones
|
||||
|
||||
SkywarnPlus can automatically change the repeater courtesy tone whenever certain weather alerts are active. The configuration for this is based on your `rpt.conf` file setup. Here's an example:
|
||||
|
||||
```ini
|
||||
[NODENUMBER]
|
||||
unlinkedct = ct1
|
||||
remotect = ct1
|
||||
linkunkeyct = ct2
|
||||
[telemetry]
|
||||
ct1 = /usr/local/bin/SkywarnPlus/SOUNDS/TONES/CT-LOCAL
|
||||
ct2 = /usr/local/bin/SkywarnPlus/SOUNDS/TONES/CT-LINK
|
||||
remotetx = /usr/local/bin/SkywarnPlus/SOUNDS/TONES/CT-LOCAL
|
||||
```
|
||||
|
||||
# Control Script
|
||||
|
||||
SkywarnPlus comes with a powerful control script (`CONTROL.sh`) that can be used to enable or disable certain SkywarnPlus functions. This script is particularly useful when you want to map DTMF control codes to these functions. An added advantage is that the script provides spoken feedback upon execution, making it even more suitable for DTMF control.
|
||||
|
||||
## Usage
|
||||
|
||||
To use the CONTROL.sh script, you need to call it with two parameters:
|
||||
|
||||
1. The name of the setting you want to change (case insensitive).
|
||||
2. The new value for the setting (either 'true' or 'false').
|
||||
|
||||
For example, to enable the SayAlert function, you would use:
|
||||
|
||||
```bash
|
||||
/usr/local/bin/SkywarnPlus/CONTROL.sh SayAlert true
|
||||
```
|
||||
|
||||
And to disable it, you would use:
|
||||
|
||||
```bash
|
||||
/usr/local/bin/SkywarnPlus/CONTROL.sh SayAlert false
|
||||
```
|
||||
|
||||
## Spoken Feedback
|
||||
|
||||
Upon the successful execution of a control command, the `CONTROL.sh` script will provide spoken feedback that corresponds to the change made. For instance, if you execute a command to enable the SayAlert function, the script will play an audio message stating that SayAlert has been enabled. This feature enhances user experience and confirms that the desired changes have been effected.
|
||||
|
||||
## Mapping to DTMF Control Codes
|
||||
|
||||
You can map the CONTROL.sh script to DTMF control codes in the `rpt.conf` file of your AllStar node. Here is an example of how to do this:
|
||||
|
||||
```bash
|
||||
901 = cmd,/usr/local/bin/SkywarnPlus/CONTROL.sh enable true ; Enables SkywarnPlus
|
||||
902 = cmd,/usr/local/bin/SkywarnPlus/CONTROL.sh enable false ; Disables SkywarnPlus
|
||||
903 = cmd,/usr/local/bin/SkywarnPlus/CONTROL.sh sayalert true ; Enables SayAlert
|
||||
904 = cmd,/usr/local/bin/SkywarnPlus/CONTROL.sh sayalert false ; Disables SayAlert
|
||||
905 = cmd,/usr/local/bin/SkywarnPlus/CONTROL.sh sayallclear true ; Enables SayAllClear
|
||||
906 = cmd,/usr/local/bin/SkywarnPlus/CONTROL.sh sayallclear false ; Disables SayAllClear
|
||||
907 = cmd,/usr/local/bin/SkywarnPlus/CONTROL.sh tailmessage true ; Enables TailMessage
|
||||
908 = cmd,/usr/local/bin/SkywarnPlus/CONTROL.sh tailmessage false ; Disables TailMessage
|
||||
909 = cmd,/usr/local/bin/SkywarnPlus/CONTROL.sh courtesytone true ; Enables CourtesyTone
|
||||
910 = cmd,/usr/local/bin/SkywarnPlus/CONTROL.sh courtesytone false ; Disables CourtesyTone
|
||||
```
|
||||
|
||||
With this setup, you can control SkywarnPlus' functionality using DTMF commands from your node.
|
||||
|
||||
# Testing
|
||||
|
||||
SkywarnPlus provides the ability to inject predefined alerts, bypassing the call to the NWS API. This feature is extremely useful for testing SkywarnPlus.
|
||||
|
||||
To enable this option, modify the following settings in the `[DEV]` section of your `config.ini` file:
|
||||
|
||||
```ini
|
||||
; Enable to inject the below list of test alerts instead of calling the NWS API
|
||||
INJECT = True
|
||||
|
||||
; CASE SENSITIVE, comma & newline separated list of alerts to inject
|
||||
INJECTALERTS = Tornado Warning,
|
||||
Tornado Watch,
|
||||
Severe Thunderstorm Warning
|
||||
```
|
||||
|
||||
# Debugging
|
||||
|
||||
Debugging is an essential part of diagnosing issues with SkywarnPlus. To facilitate this, SkywarnPlus provides a built-in debugging feature. Here's how to use it:
|
||||
|
||||
1. **Enable Debugging**: The debugging feature can be enabled in the `config.ini` file. Open this file and set the `debug` option under the `[SkywarnPlus]` section to `true`.
|
||||
|
||||
```ini
|
||||
; Logging Options
|
||||
[Logging]
|
||||
; Enable more verbose logging
|
||||
; Either True or False
|
||||
Debug = False
|
||||
```
|
||||
|
||||
This will allow the program to output detailed information about its operations, which is helpful for identifying any issues or errors.
|
||||
|
||||
2. **Open an Asterisk Console**: While debugging SkywarnPlus, it's helpful to have an Asterisk console open in a separate terminal window. This allows you to observe any issues related to Asterisk, such as problems playing audio files.
|
||||
|
||||
You can open an Asterisk console with the following command:
|
||||
|
||||
```bash
|
||||
asterisk -rvvv
|
||||
```
|
||||
|
||||
This command will launch an Asterisk console with a verbose output level of 3 (`vvv`), which provides a detailed look at what Asterisk is doing. This can be particularly useful if you're trying to debug issues with audio playback.
|
||||
|
||||
3. **Analyze Debugging Output**: With debugging enabled in SkywarnPlus and the Asterisk console open, you can now run SkywarnPlus and observe the detailed output in both terminals. This information can be used to identify and troubleshoot any issues or unexpected behaviors.
|
||||
|
||||
Remember, the more detailed your debug output is, the easier it will be to spot any issues. However, please be aware that enabling debug mode can result in large amounts of output, so it should be used judiciously.
|
||||
|
||||
If you encounter any issues that you're unable to resolve, please don't hesitate to submit a detailed bug report on the [SkywarnPlus GitHub Repository](https://github.com/mason10198/SkywarnPlus).
|
||||
|
||||
# Maintenance and Bug Reporting
|
||||
|
||||
SkywarnPlus is actively maintained by a single individual who dedicates their spare time to improve and manage this project. Despite best efforts, the application may have some bugs or areas for improvement.
|
||||
|
||||
If you encounter any issues with SkywarnPlus, please check back to the [SkywarnPlus GitHub Repository](https://github.com/mason10198/SkywarnPlus) to see if there have been any updates or fixes since the last time you downloaded it. New commits are made regularly to enhance the system's performance and rectify any known issues.
|
||||
|
||||
Bug reporting is greatly appreciated as it helps to improve SkywarnPlus. If you spot a bug, please raise an issue in the GitHub repository detailing the problem. Include as much information as possible, such as error messages, screenshots, and steps to reproduce the issue. This will assist in quickly understanding and resolving the issue.
|
||||
|
||||
Thank you for your understanding and assistance in making SkywarnPlus a more robust and reliable system for all.
|
||||
|
||||
# Contributing
|
||||
|
||||
SkywarnPlus is open-source and welcomes contributions. If you'd like to contribute, please fork the repository and use a feature branch. Pull requests are warmly welcome.
|
||||
|
||||
# License
|
||||
|
||||
SkywarnPlus is open-sourced software licensed under the [MIT license](LICENSE).
|
||||
@ -0,0 +1,85 @@
|
||||
SkywarnPlus Audio Library Dictionary
|
||||
----------------------------------------
|
||||
This file does not actually do anything.
|
||||
This is just a reference for which audio
|
||||
files correspond to which weather events.
|
||||
This is useful for those who may want to
|
||||
replace the audio files with their own.
|
||||
----------------------------------------
|
||||
SWP01.wav: Hurricane Force Wind Warning
|
||||
SWP02.wav: Severe Thunderstorm Warning
|
||||
SWP03.wav: Severe Thunderstorm Watch
|
||||
SWP04.wav: Winter Weather Advisory
|
||||
SWP05.wav: Tropical Storm Warning
|
||||
SWP06.wav: Special Marine Warning
|
||||
SWP07.wav: Freezing Rain Advisory
|
||||
SWP08.wav: Special Weather Statement
|
||||
SWP09.wav: Excessive Heat Warning
|
||||
SWP10.wav: Coastal Flood Advisory
|
||||
SWP11.wav: Coastal Flood Warning
|
||||
SWP12.wav: Winter Storm Warning
|
||||
SWP13.wav: Tropical Storm Watch
|
||||
SWP14.wav: Thunderstorm Warning
|
||||
SWP15.wav: Small Craft Advisory
|
||||
SWP16.wav: Extreme Wind Warning
|
||||
SWP17.wav: Excessive Heat Watch
|
||||
SWP18.wav: Wind Chill Advisory
|
||||
SWP19.wav: Storm Surge Warning
|
||||
SWP20.wav: River Flood Warning
|
||||
SWP21.wav: Flash Flood Warning
|
||||
SWP22.wav: Coastal Flood Watch
|
||||
SWP23.wav: Winter Storm Watch
|
||||
SWP24.wav: Wind Chill Warning
|
||||
SWP25.wav: Thunderstorm Watch
|
||||
SWP26.wav: Fire Weather Watch
|
||||
SWP27.wav: Dense Fog Advisory
|
||||
SWP28.wav: Storm Surge Watch
|
||||
SWP29.wav: River Flood Watch
|
||||
SWP30.wav: Ice Storm Warning
|
||||
SWP31.wav: Hurricane Warning
|
||||
SWP32.wav: High Wind Warning
|
||||
SWP33.wav: Flash Flood Watch
|
||||
SWP34.wav: Red Flag Warning
|
||||
SWP35.wav: Blizzard Warning
|
||||
SWP36.wav: Tornado Warning
|
||||
SWP37.wav: Hurricane Watch
|
||||
SWP38.wav: High Wind Watch
|
||||
SWP39.wav: Frost Advisory
|
||||
SWP40.wav: Freeze Warning
|
||||
SWP41.wav: Wind Advisory
|
||||
SWP42.wav: Tornado Watch
|
||||
SWP43.wav: Storm Warning
|
||||
SWP44.wav: Heat Advisory
|
||||
SWP45.wav: Flood Warning
|
||||
SWP46.wav: Gale Warning
|
||||
SWP47.wav: Freeze Watch
|
||||
SWP48.wav: Flood Watch
|
||||
SWP49.wav: Flood Advisory
|
||||
SWP50.wav: Hurricane Local Statement
|
||||
SWP51.wav: Beach Hazards Statement
|
||||
SWP52.wav: Air Quality Alert
|
||||
SWP53.wav: Severe Weather Statement
|
||||
SWP54.wav: Winter Storm Advisory
|
||||
SWP55.wav: Tropical Storm Advisory
|
||||
SWP56.wav: Blizzard Watch
|
||||
SWP57.wav: Dust Storm Warning
|
||||
SWP58.wav: High Surf Advisory
|
||||
SWP59.wav: Heat Watch
|
||||
SWP60.wav: Freeze Watch
|
||||
SWP61.wav: Dense Smoke Advisory
|
||||
SWP62.wav: Avalanche Warning
|
||||
SWP85.wav: SkywarnPlus Enabled
|
||||
SWP86.wav: SkywarnPlus Disabled
|
||||
SWP87.wav: SayAlert Enabled
|
||||
SWP88.wav: SayAlert Disabled
|
||||
SWP89.wav: SayAllClear Enabled
|
||||
SWP90.wav: SayAllClear Disabled
|
||||
SWP91.wav: Tailmessage Enabled
|
||||
SWP92.wav: Tailmessage Disabled
|
||||
SWP93.wav: CourtesyTone Enabled
|
||||
SWP94.wav: CourtesyTone Disabled
|
||||
SWP95.wav: Tic Sound Effect
|
||||
SWP96.wav: All Clear Message
|
||||
SWP97.wav: Updated Weather Information Message
|
||||
SWP98.wav: Error Sound Effect
|
||||
SWP99.wav: Word Space Silence
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,630 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
SkywarnPlus by Mason Nelson (N5LSN/WRKF394)
|
||||
==================================================
|
||||
SkywarnPlus is a utility that retrieves severe weather alerts from the National
|
||||
Weather Service and integrates these alerts with an Asterisk/app_rpt based
|
||||
radio repeater controller.
|
||||
|
||||
This utility is designed to be highly configurable, allowing users to specify
|
||||
particular counties for which to check for alerts, the types of alerts to include
|
||||
or block, and how these alerts are integrated into their radio repeater system.
|
||||
|
||||
This includes features such as automatic voice alerts and a tail message feature
|
||||
for constant updates. All alerts are sorted by severity and cover a broad range
|
||||
of weather conditions such as hurricane warnings, thunderstorms, heat waves, etc.
|
||||
|
||||
Configurable through a .ini file, SkywarnPlus serves as a comprehensive and
|
||||
flexible tool for those who need to stay informed about weather conditions
|
||||
and disseminate this information through their radio repeater system.
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import logging
|
||||
import requests
|
||||
import configparser
|
||||
import shutil
|
||||
import fnmatch
|
||||
import subprocess
|
||||
import time
|
||||
from datetime import datetime, timezone
|
||||
from dateutil import parser
|
||||
from pydub import AudioSegment
|
||||
|
||||
# Configuration file handling
|
||||
baseDir = os.path.dirname(os.path.realpath(__file__))
|
||||
configPath = os.path.join(baseDir, "config.ini")
|
||||
config = configparser.ConfigParser()
|
||||
config.read_file(open(configPath, "r"))
|
||||
|
||||
# Fetch values from configuration file
|
||||
master_enable = config["SKYWARNPLUS"].getboolean("Enable", fallback=False)
|
||||
if not master_enable:
|
||||
print("SkywarnPlus is disabled in config.ini, exiting...")
|
||||
exit()
|
||||
tmp_dir = config["DEV"].get("TmpDir", fallback="/tmp/SkywarnPlus")
|
||||
sounds_path = config["Alerting"].get("SoundsPath", fallback="./SOUNDS")
|
||||
if sounds_path == "./SOUNDS":
|
||||
sounds_path = os.path.join(baseDir, "SOUNDS")
|
||||
countyCodes = config["Alerting"]["CountyCodes"].split(",")
|
||||
|
||||
# If temporary directory doesn't exist, create it
|
||||
if not os.path.exists(tmp_dir):
|
||||
os.makedirs(tmp_dir)
|
||||
|
||||
# List of blocked events
|
||||
blocked_events = config["Blocking"].get("BlockedEvents").split(",")
|
||||
# Configuration for tailmessage
|
||||
tailmessage_config = config["Tailmessage"]
|
||||
# Flag to enable/disable tailmessage
|
||||
enable_tailmessage = tailmessage_config.getboolean("Enable", fallback=False)
|
||||
# Path to tailmessage file
|
||||
tailmessage_file = tailmessage_config.get(
|
||||
"TailmessagePath", fallback="./SOUNDS/wx-tail.wav"
|
||||
)
|
||||
if tailmessage_file == "./SOUNDS/wx-tail.wav":
|
||||
tailmessage_file = os.path.join(baseDir, "SOUNDS/wx-tail.wav")
|
||||
|
||||
# Warning and announcement strings
|
||||
WS = [
|
||||
"Hurricane Force Wind Warning",
|
||||
"Severe Thunderstorm Warning",
|
||||
"Severe Thunderstorm Watch",
|
||||
"Winter Weather Advisory",
|
||||
"Tropical Storm Warning",
|
||||
"Special Marine Warning",
|
||||
"Freezing Rain Advisory",
|
||||
"Special Weather Statement",
|
||||
"Excessive Heat Warning",
|
||||
"Coastal Flood Advisory",
|
||||
"Coastal Flood Warning",
|
||||
"Winter Storm Warning",
|
||||
"Tropical Storm Watch",
|
||||
"Thunderstorm Warning",
|
||||
"Small Craft Advisory",
|
||||
"Extreme Wind Warning",
|
||||
"Excessive Heat Watch",
|
||||
"Wind Chill Advisory",
|
||||
"Storm Surge Warning",
|
||||
"River Flood Warning",
|
||||
"Flash Flood Warning",
|
||||
"Coastal Flood Watch",
|
||||
"Winter Storm Watch",
|
||||
"Wind Chill Warning",
|
||||
"Thunderstorm Watch",
|
||||
"Fire Weather Watch",
|
||||
"Dense Fog Advisory",
|
||||
"Storm Surge Watch",
|
||||
"River Flood Watch",
|
||||
"Ice Storm Warning",
|
||||
"Hurricane Warning",
|
||||
"High Wind Warning",
|
||||
"Flash Flood Watch",
|
||||
"Red Flag Warning",
|
||||
"Blizzard Warning",
|
||||
"Tornado Warning",
|
||||
"Hurricane Watch",
|
||||
"High Wind Watch",
|
||||
"Frost Advisory",
|
||||
"Freeze Warning",
|
||||
"Wind Advisory",
|
||||
"Tornado Watch",
|
||||
"Storm Warning",
|
||||
"Heat Advisory",
|
||||
"Flood Warning",
|
||||
"Gale Warning",
|
||||
"Freeze Watch",
|
||||
"Flood Watch",
|
||||
"Flood Advisory",
|
||||
"Hurricane Local Statement",
|
||||
"Beach Hazards Statement",
|
||||
"Air Quality Alert",
|
||||
"Severe Weather Statement",
|
||||
"Winter Storm Advisory",
|
||||
"Tropical Storm Advisory",
|
||||
"Blizzard Watch",
|
||||
"Dust Storm Warning",
|
||||
"High Surf Advisory",
|
||||
"Heat Watch",
|
||||
"Freeze Watch",
|
||||
"Dense Smoke Advisory",
|
||||
"Avalanche Warning",
|
||||
"SkywarnPlus Enabled",
|
||||
"SkywarnPlus Disabled",
|
||||
"SayAlert Enabled",
|
||||
"SayAlert Disabled",
|
||||
"SayAllClear Enabled",
|
||||
"SayAllClear Disabled",
|
||||
"Tailmessage Enabled",
|
||||
"Tailmessage Disabled",
|
||||
"CourtesyTone Enabled",
|
||||
"CourtesyTone Disabled",
|
||||
"Tic Sound Effect",
|
||||
"All Clear Message",
|
||||
"Updated Weather Information Message",
|
||||
"Error Sound Effect",
|
||||
"Word Space Silence",
|
||||
]
|
||||
WA = [
|
||||
"01",
|
||||
"02",
|
||||
"03",
|
||||
"04",
|
||||
"05",
|
||||
"06",
|
||||
"07",
|
||||
"08",
|
||||
"09",
|
||||
"10",
|
||||
"11",
|
||||
"12",
|
||||
"13",
|
||||
"14",
|
||||
"15",
|
||||
"16",
|
||||
"17",
|
||||
"18",
|
||||
"19",
|
||||
"20",
|
||||
"21",
|
||||
"22",
|
||||
"23",
|
||||
"24",
|
||||
"25",
|
||||
"26",
|
||||
"27",
|
||||
"28",
|
||||
"29",
|
||||
"30",
|
||||
"31",
|
||||
"32",
|
||||
"33",
|
||||
"34",
|
||||
"35",
|
||||
"36",
|
||||
"37",
|
||||
"38",
|
||||
"39",
|
||||
"40",
|
||||
"41",
|
||||
"42",
|
||||
"43",
|
||||
"44",
|
||||
"45",
|
||||
"46",
|
||||
"47",
|
||||
"48",
|
||||
"49",
|
||||
"50",
|
||||
"51",
|
||||
"52",
|
||||
"53",
|
||||
"54",
|
||||
"55",
|
||||
"56",
|
||||
"57",
|
||||
"58",
|
||||
"59",
|
||||
"60",
|
||||
"61",
|
||||
"62",
|
||||
"85",
|
||||
"86",
|
||||
"87",
|
||||
"88",
|
||||
"89",
|
||||
"90",
|
||||
"91",
|
||||
"92",
|
||||
"93",
|
||||
"94",
|
||||
"95",
|
||||
"96",
|
||||
"97",
|
||||
"98",
|
||||
"99",
|
||||
]
|
||||
|
||||
# Cleanup flag for testing
|
||||
CLEANSLATE = config["DEV"].get("CLEANSLATE")
|
||||
if CLEANSLATE == "True":
|
||||
shutil.rmtree(tmp_dir)
|
||||
os.mkdir(tmp_dir)
|
||||
|
||||
# Configure logging
|
||||
log_config = config["Logging"]
|
||||
enable_debug = log_config.getboolean("Debug", fallback=False)
|
||||
log_file = log_config.get("LogPath", fallback="{}/SkywarnPlus.log".format(tmp_dir))
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.setLevel(logging.DEBUG if enable_debug else logging.INFO)
|
||||
c_handler = logging.StreamHandler()
|
||||
f_handler = logging.FileHandler(log_file)
|
||||
c_format = f_format = logging.Formatter("%(asctime)s %(levelname)s %(message)s")
|
||||
c_handler.setFormatter(c_format)
|
||||
f_handler.setFormatter(f_format)
|
||||
logger.addHandler(c_handler)
|
||||
logger.addHandler(f_handler)
|
||||
|
||||
# Debugging stuff
|
||||
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))
|
||||
|
||||
|
||||
def getAlerts(countyCodes):
|
||||
"""
|
||||
Retrieve severe weather alerts for specified county codes.
|
||||
|
||||
Args:
|
||||
countyCodes (list): List of county codes.
|
||||
|
||||
Returns:
|
||||
alerts (list): List of active weather alerts.
|
||||
"""
|
||||
# Check if we need to inject alerts from the config
|
||||
if config.getboolean("DEV", "INJECT", fallback=False):
|
||||
logger.debug("DEV Alert Injection Enabled")
|
||||
alerts = [
|
||||
alert.strip() for alert in config["DEV"].get("INJECTALERTS").split(",")
|
||||
]
|
||||
logger.debug("Injecting alerts: {}".format(alerts))
|
||||
return alerts
|
||||
|
||||
alerts = set() # Change list to set to automatically avoid duplicate alerts
|
||||
current_time = datetime.now(timezone.utc)
|
||||
logger.debug("Checking for alerts in {}".format(countyCodes))
|
||||
for countyCode in countyCodes:
|
||||
logger.debug("Checking for alerts in {}".format(countyCode))
|
||||
url = "https://api.weather.gov/alerts/active?zone={}".format(countyCode)
|
||||
logger.debug("Requesting {}".format(url))
|
||||
response = requests.get(url)
|
||||
logger.debug("Response: {}\n\n".format(response.text))
|
||||
|
||||
if response.status_code == 200:
|
||||
alert_data = response.json()
|
||||
for feature in alert_data["features"]:
|
||||
expires = feature["properties"].get("expires")
|
||||
if expires:
|
||||
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):
|
||||
logger.debug(
|
||||
"Blocking {} as per configuration".format(event)
|
||||
)
|
||||
break
|
||||
else:
|
||||
alerts.add(event) # Add event to set
|
||||
logger.debug("{}: {}".format(countyCode, event))
|
||||
else:
|
||||
logger.error(
|
||||
"Failed to retrieve alerts for {}, HTTP status code {}, response: {}".format(
|
||||
countyCode, response.status_code, response.text
|
||||
)
|
||||
)
|
||||
|
||||
alerts = list(alerts) # Convert set back to list
|
||||
alerts.sort(key=lambda x: WS.index(x) if x in WS else len(WS))
|
||||
return alerts
|
||||
|
||||
|
||||
def sayAlert(alerts):
|
||||
"""
|
||||
Generate and broadcast severe weather alert sounds on Asterisk.
|
||||
|
||||
Args:
|
||||
alerts (list): List of active weather alerts.
|
||||
"""
|
||||
alert_file = "{}/alert.wav".format(sounds_path)
|
||||
combined_sound = AudioSegment.from_wav(
|
||||
os.path.join(sounds_path, "ALERTS", "SWP97.wav")
|
||||
)
|
||||
sound_effect = AudioSegment.from_wav(
|
||||
os.path.join(sounds_path, "ALERTS", "SWP95.wav")
|
||||
)
|
||||
|
||||
for alert in alerts:
|
||||
try:
|
||||
index = WS.index(alert)
|
||||
audio_file = AudioSegment.from_wav(
|
||||
os.path.join(sounds_path, "ALERTS", "SWP{}.wav".format(WA[index]))
|
||||
)
|
||||
combined_sound += sound_effect + audio_file
|
||||
logger.debug("Added {} (SWP{}.wav) to alert sound".format(alert, WA[index]))
|
||||
except ValueError:
|
||||
logger.error("Alert not found: {}".format(alert))
|
||||
except FileNotFoundError:
|
||||
logger.error(
|
||||
"Audio file not found: {}/ALERTS/SWP{}.wav".format(
|
||||
sounds_path, WA[index]
|
||||
)
|
||||
)
|
||||
|
||||
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(",")
|
||||
|
||||
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)
|
||||
|
||||
|
||||
def sayAllClear():
|
||||
"""
|
||||
Generate and broadcast 'all clear' message on Asterisk.
|
||||
"""
|
||||
alert_clear = os.path.join(sounds_path, "ALERTS", "SWP96.wav")
|
||||
node_numbers = config["Asterisk"]["Nodes"].split(",")
|
||||
|
||||
for node_number in node_numbers:
|
||||
logger.info("Broadcasting all clear message on node {}".format(node_number))
|
||||
command = '/usr/sbin/asterisk -rx "rpt localplay {} {}"'.format(
|
||||
node_number.strip(), os.path.splitext(os.path.abspath(alert_clear))[0]
|
||||
)
|
||||
subprocess.run(command, shell=True)
|
||||
|
||||
|
||||
def buildTailmessage(alerts):
|
||||
"""
|
||||
Build a tailmessage, which is a short message appended to the end of a
|
||||
transmission to update on the weather conditions.
|
||||
|
||||
Args:
|
||||
alerts (list): List of active weather alerts.
|
||||
"""
|
||||
if not alerts:
|
||||
logger.debug("No alerts, creating silent tailmessage")
|
||||
silence = AudioSegment.silent(duration=100)
|
||||
converted_silence = convert_audio(silence)
|
||||
converted_silence.export(tailmessage_file, format="wav")
|
||||
return
|
||||
combined_sound = AudioSegment.empty()
|
||||
sound_effect = AudioSegment.from_wav(
|
||||
os.path.join(sounds_path, "ALERTS", "SWP95.wav")
|
||||
)
|
||||
for alert in alerts:
|
||||
try:
|
||||
index = WS.index(alert)
|
||||
audio_file = AudioSegment.from_wav(
|
||||
os.path.join(sounds_path, "ALERTS", "SWP{}.wav".format(WA[index]))
|
||||
)
|
||||
combined_sound += sound_effect + audio_file
|
||||
logger.debug("Added {} (SWP{}.wav) to tailmessage".format(alert, WA[index]))
|
||||
except ValueError:
|
||||
logger.error("Alert not found: {}".format(alert))
|
||||
except FileNotFoundError:
|
||||
logger.error(
|
||||
"Audio file not found: {}/ALERTS/SWP{}.wav".format(
|
||||
sounds_path, WA[index]
|
||||
)
|
||||
)
|
||||
logger.debug("Exporting tailmessage to {}".format(tailmessage_file))
|
||||
converted_combined_sound = convert_audio(combined_sound)
|
||||
converted_combined_sound.export(tailmessage_file, format="wav")
|
||||
|
||||
|
||||
def changeCT(ct):
|
||||
"""
|
||||
Change the current Courtesy Tone (CT) to the one specified.
|
||||
The function first checks if the specified CT is already in use, and if it is, it returns without making any changes.
|
||||
If the CT needs to be changed, it replaces the current CT files with the new ones and updates the state file.
|
||||
If no CT is specified, the function logs an error message and returns.
|
||||
|
||||
Args:
|
||||
ct (str): The name of the new CT to use. This should be one of the CTs specified in the config file.
|
||||
|
||||
Returns:
|
||||
bool: True if the CT was changed, False otherwise.
|
||||
"""
|
||||
tone_dir = config["CourtesyTones"].get("ToneDir", fallback="./SOUNDS/TONES")
|
||||
if tone_dir == "./SOUNDS/TONES":
|
||||
tone_dir = os.path.join(sounds_path, "TONES")
|
||||
local_ct = config["CourtesyTones"].get("LocalCT")
|
||||
link_ct = config["CourtesyTones"].get("LinkCT")
|
||||
wx_ct = config["CourtesyTones"].get("WXCT")
|
||||
rpt_local_ct = config["CourtesyTones"].get("RptLocalCT")
|
||||
rpt_link_ct = config["CourtesyTones"].get("RptLinkCT")
|
||||
ct_state_file = os.path.join(tmp_dir, "ct_state.txt")
|
||||
|
||||
logger.debug("Tone directory: {}".format(tone_dir))
|
||||
logger.debug("CT argument: {}".format(ct))
|
||||
|
||||
if not ct:
|
||||
logger.error("ChangeCT called with no CT specified")
|
||||
return
|
||||
|
||||
current_ct = None
|
||||
if os.path.exists(ct_state_file):
|
||||
with open(ct_state_file, "r") as file:
|
||||
current_ct = file.read().strip()
|
||||
|
||||
logger.debug("Current CT: {}".format(current_ct))
|
||||
|
||||
if ct == current_ct:
|
||||
logger.debug("Courtesy tones are already {}, no changes made.".format(ct))
|
||||
return False
|
||||
|
||||
if ct == "NORMAL":
|
||||
logger.info("Changing to NORMAL courtesy tones")
|
||||
src_file = tone_dir + "/" + local_ct
|
||||
dest_file = tone_dir + "/" + rpt_local_ct
|
||||
logger.debug("Copying {} to {}".format(src_file, dest_file))
|
||||
shutil.copyfile(src_file, dest_file)
|
||||
|
||||
src_file = tone_dir + "/" + link_ct
|
||||
dest_file = tone_dir + "/" + rpt_link_ct
|
||||
logger.debug("Copying {} to {}".format(src_file, dest_file))
|
||||
shutil.copyfile(src_file, dest_file)
|
||||
else:
|
||||
logger.info("Changing to {} courtesy tone".format(ct))
|
||||
src_file = tone_dir + "/" + wx_ct
|
||||
dest_file = tone_dir + "/" + rpt_local_ct
|
||||
logger.debug("Copying {} to {}".format(src_file, dest_file))
|
||||
shutil.copyfile(src_file, dest_file)
|
||||
|
||||
src_file = tone_dir + "/" + wx_ct
|
||||
dest_file = tone_dir + "/" + rpt_link_ct
|
||||
logger.debug("Copying {} to {}".format(src_file, dest_file))
|
||||
shutil.copyfile(src_file, dest_file)
|
||||
|
||||
with open(ct_state_file, "w") as file:
|
||||
file.write(ct)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def send_pushover_notification(message, title=None, priority=0):
|
||||
"""
|
||||
Send a push notification via Pushover service.
|
||||
The function constructs the payload for the request, including the user key, API token, message, title, and priority.
|
||||
The payload is then sent to the Pushover API endpoint. If the request fails, an error message is logged.
|
||||
|
||||
Args:
|
||||
message (str): The content of the push notification.
|
||||
title (str, optional): The title of the push notification. Defaults to None.
|
||||
priority (int, optional): The priority of the push notification. Defaults to 0.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
pushover_config = config["Pushover"]
|
||||
user_key = pushover_config.get("UserKey")
|
||||
token = pushover_config.get("APIToken")
|
||||
|
||||
# Remove newline from the end of the message
|
||||
message = message.rstrip("\n")
|
||||
|
||||
url = "https://api.pushover.net/1/messages.json"
|
||||
payload = {
|
||||
"token": token,
|
||||
"user": user_key,
|
||||
"message": message,
|
||||
"title": title,
|
||||
"priority": priority,
|
||||
}
|
||||
|
||||
response = requests.post(url, data=payload)
|
||||
|
||||
if response.status_code != 200:
|
||||
logger.error("Failed to send Pushover notification: {}".format(response.text))
|
||||
|
||||
|
||||
def convert_audio(audio):
|
||||
"""
|
||||
Convert audio file to 8000Hz mono for compatibility with Asterisk.
|
||||
|
||||
Args:
|
||||
audio (AudioSegment): Audio file to be converted.
|
||||
|
||||
Returns:
|
||||
AudioSegment: Converted audio file.
|
||||
"""
|
||||
return audio.set_frame_rate(8000).set_channels(1)
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Main function of the script, that fetches and processes severe weather
|
||||
alerts, then integrates these alerts into an Asterisk/app_rpt based radio
|
||||
repeater system.
|
||||
"""
|
||||
say_alert_enabled = config["Alerting"].getboolean("SayAlert", fallback=False)
|
||||
say_all_clear_enabled = config["Alerting"].getboolean("SayAllClear", fallback=False)
|
||||
alerts = getAlerts(countyCodes)
|
||||
tmp_file = "{}/alerts.json".format(tmp_dir)
|
||||
|
||||
if os.path.exists(tmp_file):
|
||||
with open(tmp_file, "r") as file:
|
||||
old_alerts = json.load(file)
|
||||
else:
|
||||
old_alerts = ["init"]
|
||||
logger.info("No previous alerts file found, starting fresh.")
|
||||
|
||||
if old_alerts != alerts:
|
||||
with open(tmp_file, "w") as file:
|
||||
json.dump(alerts, file)
|
||||
|
||||
ct_alerts = [
|
||||
alert.strip()
|
||||
for alert in config["CourtesyTones"].get("CTAlerts").split(",")
|
||||
]
|
||||
enable_ct_auto_change = config["CourtesyTones"].getboolean(
|
||||
"Enable", fallback=False
|
||||
)
|
||||
|
||||
pushover_enabled = config["Pushover"].getboolean("Enable", fallback=False)
|
||||
pushover_debug = config["Pushover"].getboolean("Debug", fallback=False)
|
||||
|
||||
if len(alerts) == 0:
|
||||
logger.info("No alerts found")
|
||||
pushover_message = "Alerts Cleared\n"
|
||||
if not os.path.exists(tmp_file):
|
||||
with open(tmp_file, "w") as file:
|
||||
json.dump([], file)
|
||||
if say_all_clear_enabled:
|
||||
sayAllClear()
|
||||
else:
|
||||
logger.info("Alerts found: {}".format(alerts))
|
||||
if say_alert_enabled:
|
||||
sayAlert(alerts)
|
||||
pushover_message = "\n".join(alerts) + "\n"
|
||||
|
||||
if enable_ct_auto_change:
|
||||
logger.debug(
|
||||
"CT auto change is enabled, alerts that require a CT change: {}".format(
|
||||
ct_alerts
|
||||
)
|
||||
)
|
||||
# Check if any alert matches ct_alerts
|
||||
if set(alerts).intersection(ct_alerts):
|
||||
for alert in alerts:
|
||||
if alert in ct_alerts:
|
||||
logger.debug("Alert {} requires a CT change".format(alert))
|
||||
if changeCT("WX"): # If the CT was actually changed
|
||||
if pushover_debug:
|
||||
pushover_message += "Changed courtesy tones to WX\n"
|
||||
break
|
||||
else: # No alerts require a CT change, revert back to normal
|
||||
logger.debug("No alerts require a CT change, reverting to normal.")
|
||||
if changeCT("NORMAL"): # If the CT was actually changed
|
||||
if pushover_debug:
|
||||
pushover_message += "Changed courtesy tones to NORMAL\n"
|
||||
else:
|
||||
logger.debug("CT auto change is not enabled")
|
||||
|
||||
if enable_tailmessage:
|
||||
buildTailmessage(alerts)
|
||||
if pushover_debug:
|
||||
if not alerts:
|
||||
pushover_message += "WX tailmessage removed\n"
|
||||
else:
|
||||
pushover_message += "Built WX tailmessage\n"
|
||||
|
||||
if pushover_enabled:
|
||||
pushover_message = pushover_message.rstrip("\n")
|
||||
logger.debug("Sending pushover notification: {}".format(pushover_message))
|
||||
send_pushover_notification(pushover_message, title="Alerts Changed")
|
||||
else:
|
||||
logger.debug("No change in alerts")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@ -0,0 +1,139 @@
|
||||
; SkywarnPlus Configuration File
|
||||
; Please update this file according to your setup and preferences
|
||||
[SKYWARNPLUS]
|
||||
; Completely enable/disable SkywarnPlus
|
||||
Enable = True
|
||||
|
||||
; Asterisk Settings
|
||||
[Asterisk]
|
||||
; Comma separated list of node numbers to broadcast alerts on
|
||||
; Example: Nodes = 1998, 1999
|
||||
Nodes =
|
||||
|
||||
; Alerting settings
|
||||
[Alerting]
|
||||
; List of county codes to alert on. FIND YOUR COUNTY CODE(S) at https://alerts.weather.gov/
|
||||
; Example: CountyCodes = ARC121,ARC021,ARC139,ARC027
|
||||
CountyCodes =
|
||||
|
||||
; Enable instant alerting when weather alerts change
|
||||
; Either True or False
|
||||
SayAlert = True
|
||||
|
||||
; Enable instant alerting when weather alerts are cleared
|
||||
; Either True or False
|
||||
SayAllClear = True
|
||||
|
||||
; Optional alternate path to the directory where sound files are stored
|
||||
; Default is SkywarnPlus/SOUNDS
|
||||
; Example: SoundsPath = /home/repeater/
|
||||
;SoundsPath =
|
||||
|
||||
; Blocking settings
|
||||
[Blocking]
|
||||
; CASE SENSITIVE list of events to ignore, comma separated. Wildcards can be used, e.g. *Statement, *Advisory
|
||||
BlockedEvents =
|
||||
|
||||
; Tail message settings
|
||||
[Tailmessage]
|
||||
; Enable building of tail message
|
||||
; Either True or False
|
||||
Enable = True
|
||||
|
||||
; Optional alternate path & filename where tail message should be saved
|
||||
; Default is SkywarnPlus/SOUNDS/wx-tail.wav
|
||||
; Example: TailmessagePath = /home/repeater/wx-tail.wav
|
||||
;TailmessagePath =
|
||||
|
||||
; Courtesy tone settings
|
||||
[CourtesyTones]
|
||||
; Enable Automatic Courtesy Tones
|
||||
; Either True or False
|
||||
Enable = True
|
||||
|
||||
; Optional alternate directory where tone files are located
|
||||
; Default is SkywarnPlus/SOUNDS/TONES
|
||||
; Example: ToneDir = /home/repeater/TONES
|
||||
;ToneDir =
|
||||
|
||||
; Sound file to use for normal local courtesy tone
|
||||
; Example: LocalCT = BOOP.ulaw
|
||||
LocalCT = BOOP.ulaw
|
||||
|
||||
; Sound file to use for normal link courtesy tone
|
||||
; Example: LinkCT = BEEP.ulaw
|
||||
LinkCT = BEEP.ulaw
|
||||
|
||||
; Sound file to use for weather courtesy tone (local and link)
|
||||
; Example: WXCT = WX-CT.ulaw
|
||||
WXCT = WX-CT.ulaw
|
||||
|
||||
; Sound file rpt.conf is looking for as local courtesy tone
|
||||
; Example: RptLocalCT = CT-LOCAL.ulaw
|
||||
RptLocalCT = CT-LOCAL.ulaw
|
||||
|
||||
; Sound file rpt.conf is looking for as link courtesy tone
|
||||
; Example: RptLinkCT = CT-LINK.ulaw
|
||||
RptLinkCT = CT-LINK.ulaw
|
||||
|
||||
; CASE SENSITIVE, comma & newline separated list of alerts that trigger the WX courtesy tone
|
||||
CTAlerts = Hurricane Force Wind Warning,
|
||||
Severe Thunderstorm Warning,
|
||||
Tropical Storm Warning,
|
||||
Winter Storm Warning,
|
||||
Thunderstorm Warning,
|
||||
Extreme Wind Warning,
|
||||
Storm Surge Warning,
|
||||
Ice Storm Warning,
|
||||
Hurricane Warning,
|
||||
Blizzard Warning,
|
||||
Tornado Warning,
|
||||
Tornado Watch
|
||||
|
||||
; Pushover settings
|
||||
; Pushover is a free notification service that SkywarnPlus can use to send alerts
|
||||
; to your phone or other devices. Visit https://pushover.net/ to sign up for free
|
||||
[Pushover]
|
||||
; Enable Pushover integration
|
||||
; Either True or False
|
||||
Enable = False
|
||||
|
||||
; Your Pushover User Key
|
||||
UserKey =
|
||||
|
||||
; Your Pushover API Token
|
||||
APIToken =
|
||||
|
||||
; Enable more verbose Pushover messaging
|
||||
; Either True or False
|
||||
Debug = False
|
||||
|
||||
; Logging Options
|
||||
[Logging]
|
||||
; Enable more verbose logging
|
||||
; Either True or False
|
||||
Debug = False
|
||||
|
||||
; Optional alternate log file path
|
||||
; Default is /tmp/Skywarnplus/SkywarnPlus.log
|
||||
; Example: LogPath = /path/to/log/file
|
||||
;LogPath =
|
||||
|
||||
; Developer options
|
||||
[DEV]
|
||||
; Delete cached data on startup
|
||||
; Either True or False
|
||||
CLEANSLATE = False
|
||||
|
||||
; Optional alternate TMP directory
|
||||
; Default is /tmp/SkywarnPlus
|
||||
; Example: TmpDir = /home/repeater/tmp/SkywarnPlus
|
||||
;TmpDir = /tmp/SkywarnPlus
|
||||
|
||||
; Enable to inject the below list of test alerts instead of calling the NWS API
|
||||
INJECT = False
|
||||
|
||||
; CASE SENSITIVE, comma & newline separated list of alerts to inject
|
||||
INJECTALERTS = Tornado Warning,
|
||||
Tornado Watch,
|
||||
Severe Thunderstorm Warning
|
||||
Loading…
Reference in new issue