diff --git a/Dockerfile b/Dockerfile index 93743a5..8cab3bd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,6 +18,7 @@ RUN set -x && \ KEPT_PACKAGES+=(python3-numpy) && \ KEPT_PACKAGES+=(python3-pandas) && \ KEPT_PACKAGES+=(python3-dateutil) && \ + KEPT_PACKAGES+=(python3-paho-mqtt) && \ KEPT_PACKAGES+=(jq) && \ KEPT_PACKAGES+=(gnuplot-nox) && \ KEPT_PACKAGES+=(lighttpd) && \ @@ -95,6 +96,8 @@ RUN set -x && \ chmod a+x /usr/share/socket30003/*.pl && \ rm -rf /run/socket30003/install-* && \ popd && \ + # Move the mqtt.py script to an executable directory + mv -f /scripts/mqtt.py /usr/local/bin/mqtt && \ # # Do some other stuff echo "alias dir=\"ls -alsv\"" >> /root/.bashrc && \ diff --git a/README.md b/README.md index 84dbb6f..690ce47 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ - [Planefence Configuration](#planefence-configuration) - [Initial docker configuration](#initial-docker-configuration) - [Planefence Settings Configuration](#planefence-settings-configuration) + - [Plane-Alert Exclusions](#plane-alert-exclusions) - [Applying your setup](#applying-your-setup) - [What does it look like when it's running?](#what-does-it-look-like-when-its-running) - [API access to your data](#api-access-to-your-data) @@ -18,7 +19,6 @@ - [Troubleshooting](#troubleshooting) - [Getting help](#getting-help) - ## What is it? This repository contains Planefence, which is an add-on to `ultrafeeder`, `readsb`, `dump1090`, or `dump1090-fa` (referred to herein as `your Feeder Station`). @@ -82,9 +82,7 @@ In the `docker-compose.yml` file, you should configure the following: - MANDATORY: First -- copy the template config file in place: `sudo cp /opt/adsb/planefence/config/planefence.config-RENAME-and-EDIT-me /opt/adsb/planefence/config/planefence.config` - MANDATORY: `sudo nano /opt/adsb/planefence/config/planefence.config` Go through all parameters - their function is explained in this file. Edit to your liking and save/exit using `ctrl-x`. THIS IS THE MOST IMPORTANT AND MANDATORY CONFIG FILE TO EDIT !!! - OPTIONAL: `sudo nano /opt/adsb/planefence/config/planefence-ignore.txt`. In this file, you can add aircraft that PlaneFence will ignore. If there are specific planes that fly too often over your home, add them here. Use 1 line per entry, and the entry can be a ICAO, flight number, etc. You can even use regular expressions if you want. Be careful -- we use this file as an input to a "grep" filter. If you put something that is broad (`.*` for example), then ALL PLANES will be filtered out. -- OPTIONAL: `sudo nano /opt/adsb/planefence/config/airlinecodes.txt`. This file maps the first 3 characters of the flight number to the names of the airlines. We scraped this list from a Wikipedia page, and it is by no means complete. Feel free to add more to them -- please add an issue at https://github.com/sdr-enthusiasts/docker-planefence/issues so we can add your changes to the default file. -- OPTIONAL: If you configured Twitter support before, `sudo nano /opt/adsb/planefence/config/.twurlrc`. You can add your back-up TWURLRC file here, if you want. -- OPTIONAL: Configure tweets to be sent. For details, see these instructions: [README](README-planetweet.md) +- OPTIONAL: `sudo nano /opt/adsb/planefence/config/airlinecodes.txt`. This file maps the first 3 characters of the flight number to the names of the airlines. We scraped this list from a Wikipedia page, and it is by no means complete. Feel free to add more to them -- please add an issue at so we can add your changes to the default file. - OPTIONAL: `sudo nano /opt/adsb/planefence/config/plane-alert-db.txt`. This is the list of tracking aircraft of Plane-Alert. It is prefilled with the planes of a number of "interesting" political players. Feel free to add your own, delete what you don't want to see, etc. Just follow the same format. - OPTIONAL: If you have multiple containers running on different web port, and you would like to consolidate them all under a single host name, then you should consider installing a "reverse web proxy". This can be done quickly and easily - see instructions [here](https://github.com/sdr-enthusiasts/docker-planefence/README-nginx-rev-proxy.md). - OPTIONAL: If you have a soundcard and microphone, adding NoiseCapt is as easy as hooking up the hardware and running another container. You can add this to your existing `docker-compose.yml` file, or run it on a different machine on the same subnet. Instructions are [here](https://github.com/sdr-enthusiasts/docker-noisecapt/). @@ -98,13 +96,16 @@ In the `docker-compose.yml` file, you should configure the following: #### Plane-Alert Exclusions In some circumstances you may wish to blacklist certain planes, or types of planes, from appearing in Plane-Alert and its Mastodon and Discord posts. This may be desireable if, for example, you're located near a military flight training base, where you could be flooded with dozens of notifications about T-6 Texan training aircraft every day, which could drown out more interesting planes. To that end, excluding planes can be accomplished using the `PA_EXCLUSIONS=` parameter in `/opt/adsb/planefence/config/planefence.config`. Currently, you may exclude whole ICAO Types (such as `TEX2` to remove all T-6 Texans), specific ICAO hexes (e.g. `AE1ECB`), specific registrations and tail codes (e.g. `N24HD` or `92-03327`), or any freeform string (e.g. `UC-12`, `Mayweather`, `Kid Rock`). Multiple exclusions should be separated by commas. Exclusions are case insensitive and spaces **must** be escaped with a slash `\`. An example: + ```yml PA_EXCLUSIONS=tex2,AE06D9,ae27fe,Floyd\ Mayweather,UC-12W ``` + This would exclude *all* T-6 Texans, the planes with ICAO hexes `AE06D9` (a Marine Corps UC-12F Huron) and `AE27FE` (a Coast Guard MH-60T), any planes with "Floyd Mayweather" anywhere in the database entry, and any planes with "UC-12W" anywhere in the database entry. URLs and image links are intentionally not searched. *Please note:* this is a **powerful feature** which may produce unintended consequences. You should verify that it's working correctly by examining the container logs after making changes to `planefence.config`. You should see, e.g.: -``` + +```text tex2 appears to be an ICAO type and is valid, entries excluded: 479 AE06D9 appears to be an ICAO hex and is valid, entries excluded: 1 ae27fe appears to be an ICAO hex and is valid, entries excluded: 1 @@ -124,9 +125,9 @@ Also note that after adding exclusions, any pre-existing entries for those exclu ## What does it look like when it's running? -- Planefence deployment example: https://planefence.com/planefence -- Plane-Alert deployment example: https://planefence.com/plane-alert -- Mastodon notifications: https://airwaves.social/@planeboston +- Planefence deployment example: +- Plane-Alert deployment example: +- Mastodon notifications: ## API access to your data @@ -134,8 +135,8 @@ Also note that after adding exclusions, any pre-existing entries for those exclu Planefence and Plane-Alert keep a limited amount of data available. By default, PlaneFence keeps 2 weeks of data around, while Plane-Alert isn't time limited. This data is accessible using a REST interface that makes use of HTTP GET. You can access this API from the directory where your Planefence or Plane-Alert web pages are deployed. For example: -- If Planefence is available at https://planefence.com/planefence, then you can reach the Planefence API at https://planefence.com/planefence/pf-query.php -- If Plane-Alert is available at https://planefence.com/plane-alert, then you can reach the Plane-Alert API at https://planefence.com/plane-alert/pa-query.php +- If Planefence is available at , then you can reach the Planefence API at +- If Plane-Alert is available at , then you can reach the Plane-Alert API at ### API parameters and usage examples @@ -147,37 +148,37 @@ Note that the `call` parameter (see below) will start with `@` followed by the c | Parameter | Description | Example | |---|---|---| -| `hex` | Hex ID to return | https://planeboston.com/planefence/pf_query.php?hex=^A[AB][A-F0-9]*&type=csv returns a CSV with any Planefence records of which the Hex IDs that start with A, followed by A or B, followed by 0 or more hexadecimal digits | -| `tail` | Call sign (flight number or tail) to return | https://planeboston.com/planefence/pf_query.php?call=^@?AAL[0-9]*&type=json returns any flights of which the call starts with "AAL" or "@AAL" followed by only numbers. (Note - the call value will start with `@` if the entry was tweeted, in which case the `tweet_url` field contains a link to the tweet.) | -| `start` | Start time, format `yyyy/MM/dd hh:mm:ss` | https://planeboston.com/planefence/pf_query.php?start=2021/12/19.*&type=csv returns all entries that started on Dec 19, 2021. | -| `end` | End time, format `yyyy/MM/dd hh:mm:ss` | https://planeboston.com/planefence/pf_query.php?end=2021/12/19.*&type=csv returns all entries that ended on Dec 19, 2021. | +| `hex` | Hex ID to return | ][A-F0-9]*&type=csv returns a CSV with any Planefence records of which the Hex IDs that start with A, followed by A or B, followed by 0 or more hexadecimal digits | +| `tail` | Call sign (flight number or tail) to return | returns any flights of which the call starts with "AAL" or "@AAL" followed by only numbers. (Note - the call value will start with `@` if the entry was tweeted, in which case the `tweet_url` field contains a link to the tweet.) | +| `start` | Start time, format `yyyy/MM/dd hh:mm:ss` | returns all entries that started on Dec 19, 2021. | +| `end` | End time, format `yyyy/MM/dd hh:mm:ss` | returns all entries that ended on Dec 19, 2021. | #### Plane-Alert Query parameters | Parameter | Description | Example | |---|---|---| -| `hex` | Hex ID to return | https://planeboston.com/plane-alert/pa_query.php?hex=^A[EF][A-F0-9]*&type=csv returns a CSV with any Planefence records of which the Hex IDs that start with A, followed by E or F, followed by 0 or more hexadecimal digits. (Note - this query returns most US military planes!) | -| `tail` | Tail number of the aircraft | https://planeboston.com/plane-alert/pa_query.php?tail=N14[0-9]NE&type=csv returns any records of which the tail starts with "N14", followed by 1 digit, followed by "NE". | -| `name` | Aircraft owner's name | https://planeboston.com/plane-alert/pa_query.php?name=%20Life\|%20MedFlight&type=csv returns any records that have " Life" or " MedFlight" in the owner's name. | -| `equipment` | Equipment make and model | https://planeboston.com/plane-alert/pa_query.php?equipment=EuroCopter returns any records of which the equipment contains the word "EuroCopter" | -| `timestamp` | Time first seen, format `yyyy/MM/dd hh:mm:ss` | https://planeboston.com/plane-alert/pa_query.php?timestamp=2022/01/03 returns any records from Jan 3, 2022. | -| `call` | Callsign as reported by aircraft | https://planeboston.com/plane-alert/pa_query.php?call=SAM returns any records of which the callsign contains "SAM". | -| `lat` | Latitude first observation, in decimal degrees | https://planeboston.com/plane-alert/pa_query.php?lat=^43 returns any records of which the latitude starts with "43" (i.e., 43 deg N) | -| `lon` | Longitude first observation, in decimal degrees | https://planeboston.com/plane-alert/pa_query.php?lon=^-68 returns any records of which the longitude starts with "-68" (i.e., 68 deg W) | +| `hex` | Hex ID to return | ][A-F0-9]*&type=csv returns a CSV with any Planefence records of which the Hex IDs that start with A, followed by E or F, followed by 0 or more hexadecimal digits. (Note - this query returns most US military planes!) | +| `tail` | Tail number of the aircraft | returns any records of which the tail starts with "N14", followed by 1 digit, followed by "NE". | +| `name` | Aircraft owner's name | returns any records that have " Life" or " MedFlight" in the owner's name. | +| `equipment` | Equipment make and model | returns any records of which the equipment contains the word "EuroCopter" | +| `timestamp` | Time first seen, format `yyyy/MM/dd hh:mm:ss` | returns any records from Jan 3, 2022. | +| `call` | Callsign as reported by aircraft | returns any records of which the callsign contains "SAM". | +| `lat` | Latitude first observation, in decimal degrees | returns any records of which the latitude starts with "43" (i.e., 43 deg N) | +| `lon` | Longitude first observation, in decimal degrees | returns any records of which the longitude starts with "-68" (i.e., 68 deg W) | ## Troubleshooting - Be patient. Some of the files won't get initialized until the first "event" happens: a plane is in PlaneFence range or is detected by Plane-Alert. This includes the planes table and the heatmap. - If your system doesn't behave as expected: check, check, double-check. Did you configure the correct container in `docker-compose.yml`? Did you edit the `planefence.config` file? - Check the logs: `docker logs -f planefence`. Some "complaining" about lost connections or files not found is normal, and will correct itself after a few minutes of operation. The logs will be quite explicit if it wants you to take action -- Check the website: http://myip:8088 should update every 80 seconds (starting about 80 seconds after the initial startup). The top of the website shows a last-updated time and the number of messages received from the feeder station. -- Plane-alert will appear at http://myip:8088/plane-alert +- Check the website: should update every 80 seconds (starting about 80 seconds after the initial startup). The top of the website shows a last-updated time and the number of messages received from the feeder station. +- Plane-alert will appear at - Twitter setup is complex and Elon will ban you if you publish anything about one of his planes. [Here](https://github.com/sdr-enthusiasts/docker-planefence#setting-up-tweeting)'s a description on what to do. We advise you to skip Twitter and send notifications to [Mastodon](README-Mastodon.md) instead. - Error "We cannot reach {host} on port 30003". This could be caused by a few things: - Did you set the correct hostname or IP address in `PF_SOCK30003HOST` in `planefence.config`? This can be: - - The name of another container in the same Docker compose stack, e.g., `ultrafeeder` or `tar1090` - - IP address or an external hostname to a different server - - IP address or an external hostname to the same server if the Docker instance of `ultrafeeder`, `tar1090`, etc. is in a different stack, e.g., adsb.im feeder image + - The name of another container in the same Docker compose stack, e.g., `ultrafeeder` or `tar1090` + - IP address or an external hostname to a different server + - IP address or an external hostname to the same server if the Docker instance of `ultrafeeder`, `tar1090`, etc. is in a different stack, e.g., adsb.im feeder image - Did you enable SBS (BaseStation -- *not* Beast!) output? Here are some hints on how to enable this: - For non-containerized `dump1090[-fa]`/`readsb`/`tar1090`: add command line option `--net-sbs-port 30003` - For containerized `readsb-protobuf`: add to the `environment:` section of your `docker-compose.yml` file: @@ -190,15 +191,15 @@ Note that the `call` parameter (see below) will start with `@` followed by the c - For users of the `ultrafeeder` container, no additional changes should be needed (see below for enabling MLAT aircraft) - If you are using multiple Docker container stacks, then you should also add `- 30003:30003` to the `ports:` section in the `docker-compose.yml` file that contains your `ultrafeeder`, `tar1090`, `readsb`, or service. - For users of `ultrafeeder`, if you want to enabled MLAT, make sure to set the following parameter in the `ultrafeeder` environment variables: - + ```yaml - READSB_FORWARD_MLAT_SBS=true ``` ## Getting help -- If you need further support, please join the #planefence channel at the [SDR Enthusiasts Discord Server](https://discord.gg/VDT25xNZzV) and look for "@kx1t" to your message. Alternatively, email me at kx1t@amsat.org. +- If you need further support, please join the #planefence channel at the [SDR Enthusiasts Discord Server](https://discord.gg/VDT25xNZzV) and look for "@kx1t" to your message. Alternatively, email me at . That's all! -![](https://media.giphy.com/media/3oKHWikxKFJhjArSXm/giphy.gif) +![funny image](https://media.giphy.com/media/3oKHWikxKFJhjArSXm/giphy.gif) diff --git a/rootfs/scripts/mqtt.py b/rootfs/scripts/mqtt.py new file mode 100755 index 0000000..ba61dcd --- /dev/null +++ b/rootfs/scripts/mqtt.py @@ -0,0 +1,71 @@ +#!/usr/bin/python3 +''' +# ----------------------------------------------------------------------------------- +# Copyright 2020-2024 Ramon F. Kolb - licensed under the terms and conditions +# of GPLv3. The terms and conditions of this license are included with the Github +# distribution of this package, and are also available here: +# https://github.com/sdr-enthusiasts/docker-planefence/ +# +# ----------------------------------------------------------------------------------- +# + +Send a message to a MQTT broker +Syntax: mqtt.py --broker [--port ] --topic --qos --client_id --message +where: +--broker: The address of the MQTT broker. +--port: The port of the MQTT broker (default is 1883). +--topic: The topic to which the message will be published. +--qos: The Quality of Service level (0, 1, or 2). +--message: The message payload to publish. +--client_id: The MQTT client identifier. +''' + +import argparse +import paho.mqtt.client as mqtt # type: ignore + +def publish_message(broker, port, topic, qos, message, client_id, username=None, password=None): + # Create an MQTT client instance + client = mqtt.Client(client_id) + + # Set username and password if provided + if username and password: + client.username_pw_set(username, password) + + try: + # Connect to the MQTT broker + client.connect(broker, port) + # Publish the message + client.publish(topic, payload=message, qos=qos) + print(f"Message '{message}' published to topic '{topic}' with QoS {qos}.") + # Disconnect from the broker + client.disconnect() + except Exception as e: + print(f"Failed to publish message: {e}") + +def main(): + # Set up command-line arguments + parser = argparse.ArgumentParser(description="MQTT Publish Command Line Tool") + parser.add_argument("--broker", required=True, help="MQTT broker address (e.g., 'localhost').") + parser.add_argument("--port", type=int, default=1883, help="MQTT broker port (default: 1883).") + parser.add_argument("--topic", required=True, help="MQTT topic to publish to.") + parser.add_argument("--qos", type=int, choices=[0, 1, 2], default=0, help="Quality of Service level (default: 0).") + parser.add_argument("--message", required=True, help="Message to publish.") + parser.add_argument("--client_id", default="mqtt_client", help="Client ID for MQTT connection (default: 'mqtt_client').") + parser.add_argument("--username", help="Username for MQTT authentication.") + parser.add_argument("--password", help="Password for MQTT authentication.") + args = parser.parse_args() + + # Publish the message + publish_message( + broker=args.broker, + port=args.port, + topic=args.topic, + qos=args.qos, + message=args.message, + client_id=args.client_id, + username=args.username, + password=args.password, + ) + +if __name__ == "__main__": + main() diff --git a/rootfs/usr/share/plane-alert/plane-alert.conf b/rootfs/usr/share/plane-alert/plane-alert.conf index d3d2f02..ef042c3 100644 --- a/rootfs/usr/share/plane-alert/plane-alert.conf +++ b/rootfs/usr/share/plane-alert/plane-alert.conf @@ -169,3 +169,14 @@ set +a NOTIFICATION_SERVER=planefence-notifier PA_MOTD= +# +# --------------------------------------------------------------------- +# MQTT_URL and MQTT_TOPIC are the URL and topic for MQTT notifications. If left empty, no MQTT notifications will be sent + MQTT_URL="" + MQTT_PORT="" + MQTT_TOPIC="" + MQTT_DATETIME_FORMAT="" + MQTT_CLIENT_ID="" + MQTT_QOS="" + MQTT_USERNAME="" + MQTT_PASSWORD="" diff --git a/rootfs/usr/share/plane-alert/plane-alert.sh b/rootfs/usr/share/plane-alert/plane-alert.sh index 38d634a..1de2fd4 100755 --- a/rootfs/usr/share/plane-alert/plane-alert.sh +++ b/rootfs/usr/share/plane-alert/plane-alert.sh @@ -27,6 +27,7 @@ # If not, see https://www.gnu.org/licenses/. # ----------------------------------------------------------------------------------- # +source /scripts/common PLANEALERTDIR=/usr/share/plane-alert # the directory where this file and planefence.py are located # ----------------------------------------------------------------------------------- # @@ -384,6 +385,94 @@ then echo "[$(date)][$APPNAME] $(sed -e 's|\\/|/|g' -e 's|\\n| |g' -e 's|%0A| |g' <<< "${TWITTEXT}")" fi + # Inject MQTT integration here: + if [[ -n "$MQTT_URL" ]]; then + # do some prep work: + PLANELINE="${ALERT_DICT["${ICAO}"]}" + IFS="," read -ra TAGLINE <<< "$PLANELINE" + + unset msg_array + declare -A msg_array + + # now put all relevant info into the associative array: + msg_array[icao]="${pa_record[0]//#/}" + msg_array[tail]="${pa_record[1]//#/}" + msg_array[squawk]="${pa_record[10]//#/}" + [[ "${pa_record[10]//#/}" == "7700 " ]] && msg_array[emergency]=true || msg_array[emergency]=false + msg_array[flight]="${pa_record[8]//#/}" + msg_array[operator]="${pa_record[2]//[&\'#]/_}"; msg_array[operator]="${msg_array[operator]//#/}" + msg_array[type]="${pa_record[3]//#/}" + msg_array[datetime]="$(date -d "${pa_record[4]} ${pa_record[5]}" "+${MQTT_DATETIME_FORMAT:-%s}")" + msg_array[tracklink]="${pa_record[9]//globe.adsbexchange.com/"$TRACKSERVICE"}" + msg_array[latitude]="${pa_record[6]}" + msg_array[longitude]="${pa_record[7]}" + + # Add any hashtags: + for i in {4..13}; do + (( i >= ${#header[@]} )) && break # don't print headers if they don't exist + if [[ "${header[i]:0:1}" == "$" ]] || [[ "${header[i]:0:2}" == '$#' ]]; then + hdr="${header[i]//[#$]/}" + hdr="${hdr// /_}" + hdr="${hdr,,}" + msg_array[$hdr]="${TAGLINE[i]}" + fi + done + + # convert $msg_array[@] into a JSON object: + MQTT_JSON="$(for i in "${!msg_array[@]}"; do printf "{\'%s\':\'%s\'}\n" "$i" "${msg_array[$i]}"; done | jq -sc add)" + + # prep the MQTT host, port, etc + unset MQTT_TOPIC MQTT_PORT MQTT_USERNAME MQTT_PASSWORD MQTT_HOST + MQTT_HOST="${MQTT_URL,,}" + MQTT_HOST="${MQTT_HOST##*:\/\/}" # strip protocol header (mqtt:// etc) + while [[ "${MQTT_HOST: -1}" == "/" ]]; do MQTT_HOST="${MQTT_HOST:0: -1}"; done # remove any trailing / from the HOST + if [[ $MQTT_HOST == *"/"* ]]; then MQTT_TOPIC="${MQTT_TOPIC:-${MQTT_HOST#*\/}}"; fi # if there's no explicitly defined topic, then use the URL's topic if that exists + MQTT_TOPIC="${MQTT_TOPIC:-$(hostname)/planealert}" # add default topic if there is still none defined + MQTT_HOST="${MQTT_HOST%%/*}" # remove everything from the first / onward + + if [[ $MQTT_HOST == *"@"* ]]; then + MQTT_USERNAME="${MQTT_USERNAME:-${MQTT_HOST%@*}}" + MQTT_PASSWORD="${MQTT_PASSWORD:-${MQTT_USERNAME#*:}}" + MQTT_USERNAME="${MQTT_USERNAME%:*}" + MQTT_HOST="${MQTT_HOST#*@}" + fi + if [[ $MQTT_HOST == *":"* ]]; then MQTT_PORT="${MQTT_PORT:-${MQTT_HOST#*:}}"; fi + MQTT_HOST="${MQTT_HOST%:*}" # finally strip the host so there's only a hostname or ip address + + + # log the message we are going to send: + echo "[$(date)][$APPNAME] Attempting to send a MQTT notification:" + echo "[$(date)][$APPNAME] MQTT Host: ${MQTT_HOST}" + echo "[$(date)][$APPNAME] MQTT Port: ${MQTT_PORT:-1883}" + echo "[$(date)][$APPNAME] MQTT Topic: ${MQTT_TOPIC}" + echo "[$(date)][$APPNAME] MQTT Client ID: ${MQTT_CLIENT_ID:-$(hostname)}" + if [[ -n "$MQTT_USERNAME" ]]; then echo "[$(date)][$APPNAME] MQTT Username: ${MQTT_USERNAME}"; fi + if [[ -n "$MQTT_PASSWORD" ]]; then echo "[$(date)][$APPNAME] MQTT Password: ${MQTT_PASSWORD}"; fi + if [[ -n "$MQTT_QOS" ]]; then echo "[$(date)][$APPNAME] MQTT QOS: ${MQTT_QOS}"; fi + echo "[$(date)][$APPNAME] MQTT Payload JSON Object: ${MQTT_JSON}" + + # send the MQTT message: + # send the MQTT message: + mqtt_string=(--broker "$MQTT_HOST") + if [[ -n "$MQTT_PORT" ]]; then mqtt_string+=(--port "$MQTT_PORT"); fi + mqtt_string+=(--topic \""$MQTT_TOPIC"\") + if [[ -n "$MQTT_QOS" ]]; then mqtt_string+=(--qos "$MQTT_QOS"); fi + mqtt_string+=(--client_id \""${MQTT_CLIENT_ID:-$(hostname)}"\") + if [[ -n "$MQTT_USERNAME" ]]; then mqtt_string+=(--username "$MQTT_USERNAME"); fi + if [[ -n "$MQTT_PASSWORD" ]]; then mqtt_string+=(--password "$MQTT_PASSWORD"); fi + mqtt_string+=(--message "'${MQTT_JSON}'") + + # shellcheck disable=SC2068 + outputmsg="$(echo ${mqtt_string[@]} | xargs mqtt)" + + if [[ "${outputmsg:0:6}" == "Failed" ]] || [[ "${outputmsg:0:5}" == "usage" ]] ; then + echo "[$(date)][$APPNAME] MQTT Delivery Error: ${outputmsg//$'\n'/ }" + else + echo "[$(date)][$APPNAME] MQTT Delivery successful!" + if chk_enabled "$MQTT_DEBUG"; then echo "[$(date)][$APPNAME] Results string: ${outputmsg//$'\n'/ }"; fi + fi + fi + # Inject Mastodon integration here: if [[ -n "$MASTODON_SERVER" ]] then diff --git a/rootfs/usr/share/planefence/pf_notify.sh b/rootfs/usr/share/planefence/pf_notify.sh index 57e573c..2e0111e 100755 --- a/rootfs/usr/share/planefence/pf_notify.sh +++ b/rootfs/usr/share/planefence/pf_notify.sh @@ -1,4 +1,5 @@ #!/bin/bash +# shellcheck shell=bash disable=SC1091,SC2034,SC2094 # PF_ALERT - a Bash shell script to send a notification to the Notification Server when a plane is detected in the # user-defined fence area. # @@ -15,6 +16,9 @@ # - Twurl by Twitter: https://github.com/twitter/twurl and https://developer.twitter.com # These packages may incorporate other software and license terms. # ----------------------------------------------------------------------------------- +# +# Load common script: +source /scripts/common # Let's see if there is a CONF file that overwrites some of the parameters already defined [[ "$PLANEFENCEDIR" == "" ]] && PLANEFENCEDIR=/usr/share/planefence [[ -f "$PLANEFENCEDIR/planefence.conf" ]] && source "$PLANEFENCEDIR/planefence.conf" @@ -61,14 +65,13 @@ CSVTMP=/tmp/pf_notify-tmp.csv # MINTIME is the minimum time (secs) we wait before sending a notification # to ensure that at least $MINTIME of audio collection (actually limited to the Planefence update runs in this period) to get a more accurste Loudness. -[[ "$TWEET_MINTIME" > 0 ]] && MINTIME=$TWEET_MINTIME || MINTIME=100 +(( TWEET_MINTIME > 0 )) && MINTIME=$TWEET_MINTIME || MINTIME=100 # $ATTRIB contains the attribution line at the bottom of the tweet -[[ "x$ATTRIB" == "x" ]] && ATTRIB="#Planefence by kx1t - docker:kx1t/planefence" +ATTRIB="${ATTRIB:-#Planefence by kx1t - docker:kx1t/planefence}" -if [ "$SOCKETCONFIG" != "" ] -then - case "$(grep "^distanceunit=" $SOCKETCONFIG |sed "s/distanceunit=//g")" in +if [[ -n "$SOCKETCONFIG" ]]; then + case "$(grep "^distanceunit=" "$SOCKETCONFIG" |sed "s/distanceunit=//g")" in nauticalmile) DISTUNIT="nm" ;; @@ -87,7 +90,7 @@ fi ALTUNIT="ft" if [ "$SOCKETCONFIG" != "" ] then - case "$(grep "^altitudeunit=" $SOCKETCONFIG |sed "s/altitudeunit=//g")" in + case "$(grep "^altitudeunit=" "$SOCKETCONFIG" |sed "s/altitudeunit=//g")" in feet) ALTUNIT="ft" ;; @@ -117,9 +120,9 @@ TWEETDATE=$(date --date="today" '+%y%m%d') [[ ! -f "$AIRLINECODES" ]] && AIRLINECODES="" CSVFILE=$CSVNAMEBASE$TWEETDATE$CSVNAMEEXT -#CSVFILE=/tmp/planefence-200526.csv + # make sure there's no stray TMP file around, so we can directly append -[ -f "$CSVTMP" ] && rm "$CSVTMP" +rm -f "$CSVTMP" #Now iterate through the CSVFILE: LOG "------------------------------" @@ -129,28 +132,29 @@ LOG "CSVFILE=$CSVFILE" # Get the hashtaggable headers, and figure out of there is a field with a # custom "$tag" header -if [ -f "$CSVFILE" ] -then - while read CSVLINE - do - XX=$(echo -n $CSVLINE | tr -d '[:cntrl:]') +if [[ -f "$CSVFILE" ]]; then + while read -r CSVLINE; do + XX=$(echo -n "$CSVLINE" | tr -d '[:cntrl:]') CSVLINE=$XX unset RECORD # Read the line, but first clean it up as it appears to have a newline in it - IFS="," read -aRECORD <<< "$CSVLINE" + IFS="," read -ra RECORD <<< "$CSVLINE" # LOG "${#RECORD[*]} records in the current line: (${RECORD[*]})" # $TIMEDIFF contains the difference in seconds between the current record and "now". # We want this to be at least $MINDIFF to avoid tweeting before all noise data is captured # $TWEET_BEHAVIOR determines if we are looking at the end time (POST -> RECORD[3]) or at the # start time (not POST -> RECORD[2]) of the observation time - [[ "$TWEET_BEHAVIOR" == "POST" ]] && TIMEDIFF=$(( $(date +%s) - $(date -d "${RECORD[3]}" +%s) )) || TIMEDIFF=$(( $(date +%s) - $(date -d "${RECORD[2]}" +%s) )) + if [[ "$TWEET_BEHAVIOR" == "POST" ]]; then + TIMEDIFF=$(( $(date +%s) - $(date -d "${RECORD[3]}" +%s) )) + else + TIMEDIFF=$(( $(date +%s) - $(date -d "${RECORD[2]}" +%s) )) + fi - if [[ "${RECORD[1]:0:1}" != "@" ]] && [[ $TIMEDIFF -gt $MINTIME ]] && [[ ( "$(grep "${RECORD[0]},@${RECORD[1]}" "$CSVFILE" | wc -l)" == "0" ) || "$TWEETEVERY" == "true" ]] + # shellcheck disable=SC2094 + if [[ "${RECORD[1]:0:1}" != "@" ]] && (( TIMEDIFF > MINTIME )) && ! grep -q "${RECORD[0]},@${RECORD[1]}" "$CSVFILE" || chk_enabled "$TWEETEVERY"; then # ^not tweeted before^ ^older than $MINTIME^ ^No previous occurrence that was notified^ ...or... ^$TWEETEVERY is true^ - then - - AIRLINE=$(/usr/share/planefence/airlinename.sh ${RECORD[1]#@} ${RECORD[0]} ) + AIRLINE=$(/usr/share/planefence/airlinename.sh "${RECORD[1]#@}" "${RECORD[0]}" ) # Create a Notification string that can be patched at the end of a URL: NOTIF_STRING="" @@ -193,7 +197,7 @@ then then # If the curl call succeeded, we have a snapshot.png file saved! TW_MEDIA_ID=$(twurl -X POST -H upload.twitter.com "/1.1/media/upload.json" -f /tmp/snapshot.png -F media | sed -n 's/.*\"media_id\":\([0-9]*\).*/\1/p') - [[ "$TW_MEDIA_ID" > 0 ]] && TWIMG="true" || TW_MEDIA_ID="" + (( TW_MEDIA_ID > 0 )) && TWIMG="true" || TW_MEDIA_ID="" fi [[ "$TWIMG" == "true" ]] && echo "Twitter Media ID=$TW_MEDIA_ID" || echo "Twitter screenshot upload unsuccessful for ${RECORD[0]}" @@ -201,12 +205,12 @@ then # send a tweet and read the link to the tweet into ${LINK[1]} if [[ "$TWIMG" == "true" ]] then - LINK=$(echo `twurl -r "status=$TWEET&media_ids=$TW_MEDIA_ID" /1.1/statuses/update.json` | tee -a /tmp/tweets.log | jq '.entities."urls" | .[] | .url' | tr -d '\"') + LINK="$(twurl -r "status=$TWEET&media_ids=$TW_MEDIA_ID" /1.1/statuses/update.json 2>&1 | tee -a /tmp/tweets.log | jq '.entities."urls" | .[] | .url' | tr -d '\"')" else - LINK=$(echo `twurl -r "status=$TWEET" /1.1/statuses/update.json` | tee -a /tmp/tweets.log | jq '.entities."urls" | .[] | .url' | tr -d '\"') + LINK="$(twurl -r "status=$TWEET" /1.1/statuses/update.json 2>&1 | tee -a /tmp/tweets.log | jq '.entities."urls" | .[] | .url' | tr -d '\"')" fi - [[ "${LINK:0:12}" == "https://t.co" ]] && echo "PlaneFence Tweet generated successfully with content: $TWEET" || echo "PlaneFence Tweet error. Twitter returned:\n$(tail -1 /tmp/tweets.log)" + [[ "${LINK:0:12}" == "https://t.co" ]] && echo "PlaneFence Tweet generated successfully with content: $TWEET" || echo "PlaneFence Tweet error. Twitter returned: $(tail -1 /tmp/tweets.log)" else LOG "(A tweet would have been sent but \$TWEETON=\"$TWEETON\")" fi @@ -221,7 +225,7 @@ then # Now write everything back to $CSVTMP ( IFS=','; echo "${RECORD[*]}" >> "$CSVTMP" ) - LOG "The record now contains $(IFS=','; echo ${RECORD[*]})" + LOG "The record now contains $(IFS=','; echo "${RECORD[*]}")" done < "$CSVFILE" # last, copy the TMP file back to the CSV file diff --git a/rootfs/usr/share/planefence/planefence.conf b/rootfs/usr/share/planefence/planefence.conf index 0c8b90a..7175a9f 100644 --- a/rootfs/usr/share/planefence/planefence.conf +++ b/rootfs/usr/share/planefence/planefence.conf @@ -377,8 +377,19 @@ set +a TRACKSERVICE= # # --------------------------------------------------------------------- +# MQTT_URL and MQTT_TOPIC are the URL and topic for MQTT notifications. If left empty, no MQTT notifications will be sent + MQTT_URL="" + MQTT_PORT="" + MQTT_TOPIC="" + MQTT_DATETIME_FORMAT="" + MQTT_CLIENT_ID="" + MQTT_QOS="" + MQTT_USERNAME="" + MQTT_PASSWORD="" +# +# --------------------------------------------------------------------- # Last, the version. Although you could change this, for tracking purposes, we'd like you to leave it to whatever the # official version number is. If you fork this software, we'd appreciate if you add on to the version number rather than # replace the entire number. That way, it's easy to understand from which version you forked. For example, VERSION=3.11-myfork-1.0 - VERSION=5.25-dev + VERSION=5.26-mqtt diff --git a/rootfs/usr/share/planefence/planefence_notify.sh b/rootfs/usr/share/planefence/planefence_notify.sh index a5ca475..8483f92 100755 --- a/rootfs/usr/share/planefence/planefence_notify.sh +++ b/rootfs/usr/share/planefence/planefence_notify.sh @@ -1,6 +1,6 @@ #!/command/with-contenv bash #shellcheck shell=bash -#shellcheck disable=SC2015,SC1091,SC2005,SC2006 +#shellcheck disable=SC2015,SC1091,SC2005,SC2006,SC2094 # PLANETWEET - a Bash shell script to send a Tweet when a plane is detected in the # user-defined fence area. # @@ -30,6 +30,9 @@ # set -a # # Let's see if there is a CONF file that overwrites some of the parameters already defined + +source /scripts/common + [[ -z "$PLANEFENCEDIR" ]] && PLANEFENCEDIR=/usr/share/planefence [[ -f "$PLANEFENCEDIR/planefence.conf" ]] && source "$PLANEFENCEDIR/planefence.conf" APPNAME="$(hostname)/planefence_notify" @@ -38,6 +41,7 @@ APPNAME="$(hostname)/planefence_notify" # HEADR determines the tags for each of the fields in the Tweet: # 0 1 2 3 4 5 6 7 8 9 10 11 HEADR=("ICAO" "Flt" "Airline" "First seen" "End Time" "Min Alt" "Min Dist" "Link" "Loudness" "Peak Audio" "Org" "Dest") + # CSVFILE termines which file name we need to look in. We're using the 'date' command to # get a filename in the form of 'planefence-200504.csv' where 200504 is yymmdd #TODAYCSV=$(date -d today +"planefence-%y%m%d.csv") @@ -156,7 +160,6 @@ getRoute() # print the result - this will be captured by the caller echo "$response" - } if [ "$1" != "" ] && [ "$1" != "reset" ] @@ -200,7 +203,7 @@ then CSVLINE=$XX unset RECORD # Read the line, but first clean it up as it appears to have a newline in it - IFS="," read -raRECORD <<< "$CSVLINE" + IFS="," read -ra RECORD <<< "$CSVLINE" # LOG "${#RECORD[*]} records in the current line: (${RECORD[*]})" # $TIMEDIFF contains the difference in seconds between the current record and "now". # We want this to be at least $MINDIFF to avoid tweeting before all noise data is captured @@ -309,7 +312,7 @@ then then echo "[$(date)][$APPNAME] Attempting to tweet or toot: $(sed -e 's|\\/|/|g' -e 's|\\n| |g' -e 's|%0A| |g' <<< "${TWEET}")" fi - # Inject Mastodone integration here: + # Inject Mastodon integration here: if [[ -n "$MASTODON_SERVER" ]] then mast_id="null" @@ -341,6 +344,87 @@ then fi fi + # Inject MQTT notification here: + if [[ -n "$MQTT_URL" ]]; then + unset msg_array + declare -A msg_array + + msg_array[icao]="${RECORD[0]}" + msg_array[flight]="${RECORD[1]#@}" + msg_array[operator]="${AIRLINE}" + if [[ -n "$ROUTE" ]]; then + if [[ "${ROUTE:0:4}" == "org:" ]]; then msg_array[origin]="${ROUTE:6}" + elif [[ "${ROUTE:0:5}" == "dest:" ]]; then msg_array[destination]="${ROUTE:7}" + else + msg_array[origin]="${ROUTE:1:3}" + msg_array[destination]="${ROUTE: -3}" + fi + fi + msg_array[first_seen]="$(date -d "${RECORD[2]}" "+${MQTT_DATETIME_FORMAT:-%s}")" + msg_array[last_seen]="$(date -d "${RECORD[3]}" "+${MQTT_DATETIME_FORMAT:-%s}")" + msg_array[min_alt]="${RECORD[4]} $ALTUNIT $ALTPARAM" + msg_array[min_dist]="${RECORD[5]} $DISTUNIT" + msg_array[link]="${RECORD[6]//globe.adsbexchange.com/$TRACKSERVICE}" + if (( RECORD[7] < 0 )); then + msg_array[peek_audio]="${RECORD[7]} dBFS" + msg_array[loudness]="$(( RECORD[7] - RECORD[11] )) dB" + fi + + # convert $msg_array[@] into a JSON object: + MQTT_JSON="$(for i in "${!msg_array[@]}"; do printf '{"%s":"%s"}\n' "$i" "${msg_array[$i]}"; done | jq -sc add)" + + + # prep the MQTT host, port, etc + unset MQTT_TOPIC MQTT_PORT MQTT_USERNAME MQTT_PASSWORD MQTT_HOST + MQTT_HOST="${MQTT_URL,,}" + MQTT_HOST="${MQTT_HOST##*:\/\/}" # strip protocol header (mqtt:// etc) + while [[ "${MQTT_HOST: -1}" == "/" ]]; do MQTT_HOST="${MQTT_HOST:0: -1}"; done # remove any trailing / from the HOST + if [[ $MQTT_HOST == *"/"* ]]; then MQTT_TOPIC="${MQTT_TOPIC:-${MQTT_HOST#*\/}}"; fi # if there's no explicitly defined topic, then use the URL's topic if that exists + MQTT_TOPIC="${MQTT_TOPIC:-$(hostname)/planefence}" # add default topic if there is still none defined + MQTT_HOST="${MQTT_HOST%%/*}" # remove everything from the first / onward + + if [[ $MQTT_HOST == *"@"* ]]; then + MQTT_USERNAME="${MQTT_USERNAME:-${MQTT_HOST%@*}}" + MQTT_PASSWORD="${MQTT_PASSWORD:-${MQTT_USERNAME#*:}}" + MQTT_USERNAME="${MQTT_USERNAME%:*}" + MQTT_HOST="${MQTT_HOST#*@}" + fi + if [[ $MQTT_HOST == *":"* ]]; then MQTT_PORT="${MQTT_PORT:-${MQTT_HOST#*:}}"; fi + MQTT_HOST="${MQTT_HOST%:*}" # finally strip the host so there's only a hostname or ip address + + + # log the message we are going to send: + echo "[$(date)][$APPNAME] Attempting to send a MQTT notification:" + echo "[$(date)][$APPNAME] MQTT Host: $MQTT_HOST" + echo "[$(date)][$APPNAME] MQTT Port: ${MQTT_PORT:-1883}" + echo "[$(date)][$APPNAME] MQTT Topic: $MQTT_TOPIC" + echo "[$(date)][$APPNAME] MQTT Client ID: ${MQTT_CLIENT_ID:-$(hostname)}" + if [[ -n "$MQTT_USERNAME" ]]; then echo "[$(date)][$APPNAME] MQTT Username: $MQTT_USERNAME"; fi + if [[ -n "$MQTT_PASSWORD" ]]; then echo "[$(date)][$APPNAME] MQTT Password: $MQTT_PASSWORD"; fi + if [[ -n "$MQTT_QOS" ]]; then echo "[$(date)][$APPNAME] MQTT QOS: $MQTT_QOS"; fi + echo "[$(date)][$APPNAME] MQTT Payload JSON Object: $MQTT_JSON" + + # send the MQTT message: + mqtt_string=(--broker "$MQTT_HOST") + if [[ -n "$MQTT_PORT" ]]; then mqtt_string+=(--port "$MQTT_PORT"); fi + mqtt_string+=(--topic \""$MQTT_TOPIC"\") + if [[ -n "$MQTT_QOS" ]]; then mqtt_string+=(--qos "$MQTT_QOS"); fi + mqtt_string+=(--client_id \""${MQTT_CLIENT_ID:-$(hostname)}"\") + if [[ -n "$MQTT_USERNAME" ]]; then mqtt_string+=(--username "$MQTT_USERNAME"); fi + if [[ -n "$MQTT_PASSWORD" ]]; then mqtt_string+=(--password "$MQTT_PASSWORD"); fi + mqtt_string+=(--message "'${MQTT_JSON}'") + + # shellcheck disable=SC2068 + outputmsg="$(echo ${mqtt_string[@]} | xargs mqtt)" + + if [[ "${outputmsg:0:6}" == "Failed" ]] || [[ "${outputmsg:0:5}" == "usage" ]] ; then + echo "[$(date)][$APPNAME] MQTT Delivery Error: ${outputmsg//$'\n'/ }" + else + echo "[$(date)][$APPNAME] MQTT Delivery successful!" + if chk_enabled "$MQTT_DEBUG"; then echo "[$(date)][$APPNAME] Results string: ${outputmsg//$'\n'/ }"; fi + fi + + fi # And now, let's tweet! if [ "$TWEETON" == "yes" ] diff --git a/rootfs/usr/share/planefence/prep-planefence.sh b/rootfs/usr/share/planefence/prep-planefence.sh index a7b3619..4bf24fe 100755 --- a/rootfs/usr/share/planefence/prep-planefence.sh +++ b/rootfs/usr/share/planefence/prep-planefence.sh @@ -377,6 +377,24 @@ configure_planealert "PA_MOTD" "\"$PA_MOTD\"" [[ -n "$PA_TRACKLIMIT" ]] && configure_planealert "TRACKLIMIT" "$PA_TRACKLIMIT" || true # #-------------------------------------------------------------------------------- +# Configure MQTT notifications for Planefence and plane-alert +[[ -n "$PF_MQTT_URL" ]] && configure_planefence "MQTT_URL" "$PF_MQTT_URL" || true +[[ -n "$PF_MQTT_CLIENT_ID" ]] && configure_planefence "MQTT_CLIENT_ID" "$PF_MQTT_CLIENT_ID" || true +[[ -n "$PF_MQTT_TOPIC" ]] && configure_planefence "MQTT_TOPIC" "$PF_MQTT_TOPIC" || true +[[ -n "$PF_MQTT_DATETIME_FORMAT" ]] && configure_planefence "MQTT_DATETIME_FORMAT" "$PF_MQTT_DATETIME_FORMAT" || true +[[ -n "$PF_MQTT_USERNAME" ]] && configure_planefence "MQTT_USERNAME" "$PF_MQTT_USERNAME" || true +[[ -n "$PF_MQTT_PASSWORD" ]] && configure_planefence "MQTT_PASSWORD" "$PF_MQTT_PASSWORD" || true +[[ -n "$PF_MQTT_QOS" ]] && configure_planefence "MQTT_QOS" "$PF_MQTT_QOS" || true + +[[ -n "$PA_MQTT_URL" ]] && configure_planealert "MQTT_URL" "$PA_MQTT_URL" || true +[[ -n "$PA_MQTT_CLIENT_ID" ]] && configure_planealert "MQTT_CLIENT_ID" "$PA_MQTT_CLIENT_ID" || true +[[ -n "$PA_MQTT_TOPIC" ]] && configure_planealert "MQTT_TOPIC" "$PA_MQTT_TOPIC" || true +[[ -n "$PA_MQTT_DATETIME_FORMAT" ]] && configure_planealert "MQTT_DATETIME_FORMAT" "$PA_MQTT_DATETIME_FORMAT" || true +[[ -n "$PA_MQTT_USERNAME" ]] && configure_planealert "MQTT_USERNAME" "$PA_MQTT_USERNAME" || true +[[ -n "$PA_MQTT_PASSWORD" ]] && configure_planealert "MQTT_PASSWORD" "$PA_MQTT_PASSWORD" || true +[[ -n "$PA_MQTT_QOS" ]] && configure_planealert "MQTT_QOS" "$PA_MQTT_QOS" || true +# +#-------------------------------------------------------------------------------- # Last thing - save the date we processed the config to disk. That way, if ~/.planefence/planefence.conf is changed, # we know that we need to re-run this prep routine! diff --git a/rootfs/usr/share/planefence/stage/planefence.config b/rootfs/usr/share/planefence/stage/planefence.config index c8074a4..2320bf1 100644 --- a/rootfs/usr/share/planefence/stage/planefence.config +++ b/rootfs/usr/share/planefence/stage/planefence.config @@ -424,3 +424,53 @@ PA_EXCLUSIONS= PF_TRACKSERVICE=globe.adsbexchange.com PA_TRACKSERVICE=globe.adsbexchange.com PA_TRACKLIMIT=250 +# +# --------------------------------------------------------------------- +# The following are MQTT related parameters for Planefence and Plane-Alert +# If defined, PF and/or PA will attempt to deliver newly onserved aircraft to a MQTT broker +# Note - at this time, only MQTT deliveries via the mqtt protocol are supported. +# This means specifically that SSL deliveries (mqtts) or WebSocket deliveries (ws:// or wss://) are not supported. + +# CLIENT_ID: Optional. Client ID string (no whitespace please). If omitted, the (container) hostname is used +# URL: Mandatory. Host (ip or hostname) or URL string for the MQTT broker. If omitted, MQTT is switched off. See below for format +# PORT: Optional. TCP port of the MQTT server The default port of 1883 is used when omitted +# TOPIC: Optional. MQTT Topic passed to the MQTT broker. If omitted, "/planefence" or "/plane-alert" is used +# DATETIME_FORMAT: Optional. Sets the format of the date/time using Linux "date" command formatting for the PA MQTT 'datetime' tag +# and the PF MQTT "first_seen" and "last_seen" tags. Default value is "%s" (seconds since epoch). See 'man date' for an overview. +# +# Format for the PF_MQTT_URL / PA_MQTT_URL parameter is: "mqtt://[user:pass]@host[:port]/[topic]", for example: +# mqtt://admin:password@192.168.0.1:1883/home/planefence ...or... +# my.mqttserver.com:1883 ...or... 192.168.0.1 +# QOS: Optional. QOS value passed to the MQTT Broker. "0" if omitted. +# USERNAME / PASSWORD: Basic Authentication username/password that is passed to the MQTT Broker. +# Please note that these values are transmitted without any encryption and may be visible to anyone monitoring your internet traffic! +# +# Examples of the format for PA_MQTT_URL and PF_MQTT_URL are below. Please note that only the hostname or IP is mandatory. +# PF_MQTT_URL=192.168.11.22 --> set the MQTT Broker host address to 192.168.11.22. (Resolvable hostnames are also acceptable) +# PF_MQTT_URL=mqtt://my.mqtt.broker.com --> set the MQTT Broker host address to my.mqtt.broker.com +# PF_MQTT_URL=mqtt://my.mqtt.broker.com:1999 --> set the MQTT Broker host address as above and the TCP Port of the MQTT broker to 1999 +# PF_MQTT_URL=mqtt://username:password@my.mqtt.broker.com --> set the MQTT Broker host address as above and set the Basic Authentication credentials +# PF_MQTT_URL=mqtt://my.mqtt.broker.com/group/topic --> set the MQTT Broker host address as above, and set the MQTT Topic to "group/topic" +# Notes - the use of the "mqtt://" prefix is optional and will be ignored when interpreting the value +# - parameters can be combined, for example mqtt://myuser@mypass:192.168.0.99:11883/mygrp/mytopic +# - if both the URL and the individual parameters have values defined, the individual parameters will take precedent +# and the parameters in the URL will be ignored. +# - only the IP address or hostname is MANDATORY +# +PF_MQTT_URL="" +PF_MQTT_PORT="" +PF_MQTT_CLIENT_ID="" +PF_MQTT_TOPIC="" +PF_MQTT_DATETIME_FORMAT="" +PF_MQTT_QOS="" +PF_MQTT_USERNAME="" +PF_MQTT_PASSWORD="" + +PA_MQTT_URL="" +PA_MQTT_PORT="" +PA_MQTT_CLIENT_ID="" +PA_MQTT_TOPIC="" +PA_MQTT_DATETIME_FORMAT="" +PA_MQTT_QOS="" +PA_MQTT_USERNAME="" +PA_MQTT_PASSWORD=""