├── .dockerignore
├── .github
├── docs-static
│ ├── CNAME
│ └── index.html
└── workflows
│ ├── gh-pages-static.yml
│ ├── release-docker-full.yml
│ ├── release-docker-standard.yml
│ └── tests.yml
├── .gitignore
├── .readthedocs.yml
├── .vscode
├── launch.json
└── settings.json
├── CHANGES.rst
├── CITATION.cff
├── Dockerfile
├── Dockerfile.full
├── Dockerfile.mqttwarn-slack
├── LICENSE
├── MANIFEST.in
├── Makefile
├── README.rst
├── assets
├── apns.png
├── desktopnotify.jpg
├── google-definition.jpg
├── gss.png
├── hipchat.png
├── icinga.jpg
├── ionic.png
├── irccat.png
├── linuxnotify.png
├── logo
│ ├── img
│ │ ├── hz_wordmark_blk_200x75.png
│ │ ├── hz_wordmark_blk_500x187.png
│ │ ├── hz_wordmark_wht_200x75.png
│ │ ├── hz_wordmark_wht_500x187.png
│ │ ├── markonly_200x200.png
│ │ ├── markonly_500x500.png
│ │ ├── markword_200x200.png
│ │ └── markword_500x500.png
│ └── src
│ │ ├── hz_wordmark_blk.svg
│ │ ├── hz_wordmark_wht.svg
│ │ ├── markonly.svg
│ │ └── wordmark.svg
├── mattermost.png
├── mqttwarn.png
├── pastebin.png
├── prowl.jpg
├── pushalot.png
├── pushbullet.jpg
├── pushover.png
├── pushsafer.jpg
├── slack.png
├── telegram.png
├── tootpaste.png
├── twilio.jpg
├── twitter.jpg
└── zabbix.png
├── codecov.yml
├── contrib
├── amqp-puka-get.py
└── zabbix_mqtt_agent.py
├── docker-compose.yml
├── docs
├── .gitignore
├── Makefile
├── assets
├── conf.py
├── configure
│ ├── index.rst
│ ├── mqttwarn.ini.md
│ ├── service.md
│ ├── task.md
│ ├── topic.md
│ └── transformation.md
├── examples
├── index.rst
├── make.bat
├── mqttwarn-logo.png
├── notifier-catalog.md
├── readme.rst
├── requirements.txt
├── usage
│ ├── freebsd.md
│ ├── index.rst
│ ├── oci.md
│ ├── pip.md
│ └── standalone.md
└── workbench
│ ├── backlog.rst
│ ├── changelog.rst
│ └── sandbox.rst
├── etc
├── OpenWRT.init
├── mqttwarn.default
├── mqttwarn.init
├── mqttwarn.logrotate
├── mqttwarn.openrc
├── mqttwarn.service
├── supervisor.ini
└── zabbix-template.xml
├── examples
├── __init__.py
├── alexa
│ ├── alexa.ini
│ ├── announce_stdin
│ ├── readme.md
│ ├── saystdin
│ └── secrets.sh
├── arduino-temperature
│ └── readme.md
├── conftest.py
├── frigate
│ ├── .env
│ ├── .gitignore
│ ├── README.rst
│ ├── assets
│ │ ├── frigate-event-end.json
│ │ ├── frigate-event-false-positive.json
│ │ ├── frigate-event-full.json
│ │ ├── frigate-event-new-good.json
│ │ ├── frigate-event-new-ignored.json
│ │ ├── frigate-event-update-good.json
│ │ ├── frigate-event-update-samezone.json
│ │ └── frigate-event-update-stationary.json
│ ├── docker-compose.yml
│ ├── frigate.ini
│ ├── frigate.py
│ ├── publish.sh
│ └── test_frigate.py
├── hiveeyes
│ ├── __init__.py
│ ├── hiveeyes.ini
│ └── hiveeyes.py
├── homie
│ ├── __init__.py
│ ├── homie.ini
│ └── homie.py
├── mediaplayer
│ ├── mqttwarn-mplayer.ini
│ └── readme.md
├── owntracks-ntfy
│ ├── mqttwarn-owntracks.ini
│ ├── mqttwarn-owntracks.py
│ └── readme.md
├── readme.md
├── warntoggle
│ ├── README.rst
│ ├── mqttwarn
│ │ └── customfunctions.py
│ └── www
│ │ ├── warntoggle.json
│ │ └── warntoggle.py
└── zabbix-iot
│ ├── README.md
│ ├── mqttwarn-zabbix-iot.ini
│ ├── mqttwarn-zabbix-iot.py
│ └── readme.md
├── mqttwarn
├── __init__.py
├── __main__.py
├── commands.py
├── configuration.py
├── context.py
├── core.py
├── cron.py
├── examples
│ ├── __init__.py
│ └── basic
│ │ ├── mqttwarn.ini
│ │ └── udf.py
├── model.py
├── services
│ ├── README.md
│ ├── __init__.py
│ ├── alexa-notify-me.py
│ ├── amqp.py
│ ├── apns.py
│ ├── apprise.py
│ ├── apprise_multi.py
│ ├── apprise_single.py
│ ├── apprise_util.py
│ ├── asterisk.py
│ ├── autoremote.py
│ ├── azure_iot.py
│ ├── carbon.py
│ ├── celery.py
│ ├── chromecast.py
│ ├── dbus.py
│ ├── desktopnotify.py
│ ├── dnsupdate.py
│ ├── emoncms.py
│ ├── execute.py
│ ├── fbchat.py
│ ├── file.py
│ ├── freeswitch.py
│ ├── gss2.py
│ ├── hangbot.py
│ ├── http_urllib.py
│ ├── icinga2.py
│ ├── ifttt.py
│ ├── influxdb.py
│ ├── ionic.py
│ ├── irccat.py
│ ├── linuxnotify.py
│ ├── log.py
│ ├── mattermost.py
│ ├── mqtt.py
│ ├── mqtt_filter.py
│ ├── mqttpub.py
│ ├── mysql.py
│ ├── mysql_dynamic.py
│ ├── mysql_remap.py
│ ├── mythtv.py
│ ├── nntp.py
│ ├── noop.py
│ ├── nsca.py
│ ├── ntfy.py
│ ├── osxsay.py
│ ├── pastebinpub.py
│ ├── pipe.py
│ ├── postgres.py
│ ├── prowl.py
│ ├── pushbullet.py
│ ├── pushover.py
│ ├── pushsafer.py
│ ├── redispub.py
│ ├── rrdtool.py
│ ├── serial.py
│ ├── slack.py
│ ├── slixmpp.py
│ ├── smtp.py
│ ├── sqlite.py
│ ├── sqlite_json2cols.py
│ ├── sqlite_timestamp.py
│ ├── ssh.py
│ ├── syslog.py
│ ├── telegram.py
│ ├── thingspeak.py
│ ├── tootpaste.py
│ ├── twilio.py
│ ├── twitter.py
│ ├── websocket.py
│ ├── xbmc.py
│ ├── xmpp.py
│ └── zabbix.py
├── testing
│ ├── __init__.py
│ └── fixtures.py
├── util.py
└── vendor
│ ├── ZabbixSender.py
│ └── __init__.py
├── pyproject.toml
├── requirements-release.txt
├── setup.py
├── templates
├── demo.j2
├── hiveeyes-alert.j2
└── test.jinja
├── tests
├── __init__.py
├── acme
│ ├── __init__.py
│ └── foobar.py
├── conftest.py
├── etc
│ ├── __init__.py
│ ├── better-addresses.ini
│ ├── empty-functions.ini
│ ├── full.ini
│ ├── functions_bad.py
│ ├── functions_good.py
│ ├── logging-levels.ini
│ ├── no-functions.ini
│ ├── password.txt
│ ├── service-loading.ini
│ └── with-variables.ini
├── fixtures
│ ├── __init__.py
│ └── ntfy.py
├── services
│ ├── __init__.py
│ ├── pushsafer
│ │ ├── __init__.py
│ │ ├── conftest.py
│ │ ├── test_pushsafer_common.py
│ │ ├── test_pushsafer_v1.py
│ │ ├── test_pushsafer_v2.py
│ │ └── util.py
│ ├── test_alexa.py
│ ├── test_amqp.py
│ ├── test_apns.py
│ ├── test_apprise_multi.py
│ ├── test_apprise_ntfy.py
│ ├── test_apprise_single.py
│ ├── test_asterisk.py
│ ├── test_autoremote.py
│ ├── test_azure.py
│ ├── test_carbon.py
│ ├── test_desktopnotify.py
│ ├── test_execute.py
│ ├── test_file.py
│ ├── test_http.py
│ ├── test_irccat.py
│ ├── test_log.py
│ ├── test_noop.py
│ ├── test_ntfy.py
│ ├── test_ntfy_integration.py
│ ├── test_pushbullet.py
│ ├── test_pushover.py
│ └── test_smtp.py
├── test_commands.py
├── test_configuration.py
├── test_context.py
├── test_core_infra.py
├── test_core_job.py
├── test_core_main.py
├── test_cron.py
├── test_e2e.py
├── test_model.py
├── test_util.py
└── util.py
└── tox.ini
/.dockerignore:
--------------------------------------------------------------------------------
1 | *
2 | !.git
3 | !etc
4 | !mqttwarn
5 | !tests
6 | !pyproject.toml
7 | !setup.py
8 | !MANIFEST.in
9 | !*.ini
10 | !*.md
11 | !*.rst
12 |
--------------------------------------------------------------------------------
/.github/docs-static/CNAME:
--------------------------------------------------------------------------------
1 | mqttwarn.readthedocs.io
--------------------------------------------------------------------------------
/.github/docs-static/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Redirecting to https://mqttwarn.readthedocs.io/
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.github/workflows/gh-pages-static.yml:
--------------------------------------------------------------------------------
1 | # Simple workflow for deploying static content to GitHub Pages
2 | name: Deploy static content to Pages
3 |
4 | on:
5 | # Runs on pushes targeting the default branch
6 | push:
7 | branches: ["main"]
8 |
9 | # Allows you to run this workflow manually from the Actions tab
10 | workflow_dispatch:
11 |
12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
13 | permissions:
14 | contents: read
15 | pages: write
16 | id-token: write
17 |
18 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
19 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
20 | concurrency:
21 | group: "pages"
22 | cancel-in-progress: false
23 |
24 | jobs:
25 | # Single deploy job since we're just deploying
26 | deploy:
27 | environment:
28 | name: github-pages
29 | url: ${{ steps.deployment.outputs.page_url }}
30 | runs-on: ubuntu-latest
31 | steps:
32 | - name: Checkout
33 | uses: actions/checkout@v3
34 | - name: Setup Pages
35 | uses: actions/configure-pages@v3
36 | - name: Upload artifact
37 | uses: actions/upload-pages-artifact@v1
38 | with:
39 | path: '.github/docs-static'
40 | - name: Deploy to GitHub Pages
41 | id: deployment
42 | uses: actions/deploy-pages@v2
43 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | name: Tests
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 | pull_request:
7 | branches: [ main ]
8 |
9 | # Allow job to be triggered manually.
10 | workflow_dispatch:
11 |
12 | # Cancel in-progress jobs when pushing to the same branch.
13 | concurrency:
14 | cancel-in-progress: true
15 | group: ${{ github.workflow }}-${{ github.ref }}
16 |
17 | jobs:
18 | tests:
19 | runs-on: ${{ matrix.os }}
20 | strategy:
21 | fail-fast: false
22 | matrix:
23 | os: ["ubuntu-20.04"]
24 | python-version: ["3.6", "3.7", "3.12"]
25 | include:
26 | - os: "macos-latest"
27 | python-version: "3.12"
28 | - os: "windows-latest"
29 | python-version: "3.11"
30 | env:
31 | OS: ${{ matrix.os }}
32 | PYTHON: ${{ matrix.python-version }}
33 |
34 | name: Python ${{ matrix.python-version }} on OS ${{ matrix.os }}
35 | steps:
36 |
37 | - name: Acquire sources
38 | uses: actions/checkout@v3
39 |
40 | # https://github.com/docker-practice/actions-setup-docker
41 | # - name: Install Docker
42 | # if: runner.os == 'Linux'
43 | # uses: docker-practice/actions-setup-docker@master
44 |
45 | - name: Display Docker version
46 | if: runner.os == 'Linux'
47 | run: |
48 | docker version
49 |
50 | - name: Setup Python
51 | uses: actions/setup-python@v4
52 | with:
53 | python-version: ${{ matrix.python-version }}
54 | architecture: x64
55 | cache: 'pip'
56 | cache-dependency-path: 'setup.py'
57 |
58 | - name: Setup project
59 | run: |
60 | # Install package in editable mode.
61 | pip install versioningit wheel
62 | pip install --editable=.[test,develop]
63 |
64 | - name: Check code style
65 | if: matrix.python-version != '3.6'
66 | run: |
67 | poe lint
68 |
69 | - name: Run tests
70 | run: |
71 | poe test
72 |
73 | - name: Upload coverage to Codecov
74 | uses: codecov/codecov-action@v3
75 | with:
76 | files: ./coverage.xml
77 | flags: unittests
78 | env_vars: OS,PYTHON
79 | name: codecov-umbrella
80 | fail_ci_if_error: false
81 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.idea
3 | /.venv*
4 | /dist*
5 | /docs/_build
6 | /build
7 | *.pyc
8 | *.egg-info
9 | *.sln
10 | *.suo
11 | *.pyproj
12 | services/winscp.rnd
13 | .vs/slnx.sqlite
14 | .vs/VSWorkspaceState.json
15 | .vs/ProjectSettings.json
16 | udf.py
17 | .pytest_cache
18 | .pytest_results
19 | .coverage*
20 | coverage.xml
21 | .tox
22 | .ruff_cache
23 |
--------------------------------------------------------------------------------
/.readthedocs.yml:
--------------------------------------------------------------------------------
1 | # .readthedocs.yml
2 | # Read the Docs configuration file
3 |
4 | # Details
5 | # - https://docs.readthedocs.io/en/stable/config-file/v2.html
6 |
7 | # Required
8 | version: 2
9 |
10 | build:
11 | os: "ubuntu-22.04"
12 | tools:
13 | python: "3.11"
14 |
15 | # Build documentation in the docs/ directory with Sphinx
16 | sphinx:
17 | configuration: docs/conf.py
18 |
19 | python:
20 | install:
21 | - requirements: docs/requirements.txt
22 |
23 | # Optionally build your docs in additional formats such as PDF
24 | # formats:
25 | # - pdf
26 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // VSCode launch configuration settings file for mqttwarn.
3 |
4 | // Use IntelliSense to learn about possible attributes.
5 | // Hover to view descriptions of existing attributes.
6 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
7 | "version": "0.2.0",
8 | "configurations": [
9 |
10 | {
11 | "name": "Launch",
12 | "type": "python",
13 | "request": "launch",
14 | "program": "mqttwarn"
15 | }
16 | ]
17 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "python.pythonPath": "${workspaceFolder}/.venv/bin/python",
3 | "python.testing.pytestEnabled": true,
4 | "python.testing.pytestPath": "${workspaceFolder}/.venv/bin/pytest"
5 | }
--------------------------------------------------------------------------------
/CITATION.cff:
--------------------------------------------------------------------------------
1 | ---
2 | cff-version: 1.2.0
3 | message: If you use this software, please cite it using these metadata.
4 |
5 | title: mqttwarn
6 | url: https://mqttwarn.readthedocs.org/
7 | abstract: mqttwarn - subscribe to MQTT topics and notify pluggable services
8 | authors:
9 | - name: Jan-Piet Mens
10 | email: jpmens@gmail.com
11 | - name: Ben Jones
12 | email: ben.jones12@gmail.com
13 | - name: Andreas Motl
14 | email: andreas.motl@panodata.org
15 |
16 | date-published: 2014-02-09
17 | date-released: 2023-04-13
18 | type: software
19 | license: EPL
20 | license-url: https://github.com/mqtt-tools/mqttwarn/blob/main/LICENSE
21 | repository-code: https://github.com/mqtt-tools/mqttwarn
22 | keywords:
23 | - acquisition
24 | - data
25 | - engine
26 | - notification
27 | - plugins
28 | - push
29 | - transformation
30 | - mosquitto
31 | - mqtt
32 | - mqttwarn
33 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # Docker build file for `mqttwarn-standard`.
2 | #
3 | # Invoke like:
4 | #
5 | # docker build --tag=local/mqttwarn-standard --file=Dockerfile .
6 | #
7 | FROM python:3.11-slim-bullseye
8 |
9 |
10 | # =====
11 | # Build
12 | # =====
13 |
14 | # Install build prerequisites.
15 | RUN rm -f /etc/apt/apt.conf.d/docker-clean; echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache
16 | RUN \
17 | --mount=type=cache,id=apt,sharing=locked,target=/var/cache/apt \
18 | --mount=type=cache,id=apt,sharing=locked,target=/var/lib/apt \
19 | true \
20 | && apt-get update \
21 | && apt-get install --no-install-recommends --no-install-suggests --yes git
22 |
23 | # Create /etc/mqttwarn
24 | RUN mkdir -p /etc/mqttwarn
25 | WORKDIR /etc/mqttwarn
26 |
27 | # Add user "mqttwarn"
28 | RUN groupadd -r mqttwarn && useradd -r -g mqttwarn mqttwarn
29 | RUN chown -R mqttwarn:mqttwarn /etc/mqttwarn
30 |
31 | # Install package.
32 | COPY . /src
33 | RUN --mount=type=cache,id=pip,target=/root/.cache/pip \
34 | true \
35 | && pip install --upgrade pip \
36 | && pip install --prefer-binary versioningit wheel \
37 | && pip install --use-pep517 --prefer-binary '/src'
38 |
39 | # Uninstall build prerequisites again.
40 | RUN apt-get --yes remove --purge git && apt-get --yes autoremove
41 |
42 | # Purge /src and /tmp directories.
43 | RUN rm -rf /src /tmp/*
44 |
45 |
46 | # =======
47 | # Runtime
48 | # =======
49 |
50 | # Make process run as "mqttwarn" user
51 | USER mqttwarn
52 |
53 | # Use configuration file from host
54 | VOLUME ["/etc/mqttwarn"]
55 |
56 | # Set default configuration path
57 | ENV MQTTWARNINI="/etc/mqttwarn/mqttwarn.ini"
58 |
59 | # Invoke program
60 | CMD mqttwarn
61 |
--------------------------------------------------------------------------------
/Dockerfile.full:
--------------------------------------------------------------------------------
1 | # Docker build file for `mqttwarn-full`.
2 | #
3 | # Invoke like:
4 | #
5 | # docker build --tag=local/mqttwarn-full --file=Dockerfile.full .
6 | #
7 | FROM python:3.11-slim-bullseye
8 |
9 |
10 | # =====
11 | # Build
12 | # =====
13 |
14 | # Install build prerequisites.
15 | RUN rm -f /etc/apt/apt.conf.d/docker-clean; echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache
16 | RUN \
17 | --mount=type=cache,id=apt,sharing=locked,target=/var/cache/apt \
18 | --mount=type=cache,id=apt,sharing=locked,target=/var/lib/apt \
19 | true \
20 | && apt-get update \
21 | && apt-get install --no-install-recommends --no-install-suggests --yes git build-essential libmariadb-dev pkg-config
22 |
23 | # Create /etc/mqttwarn
24 | RUN mkdir -p /etc/mqttwarn
25 | WORKDIR /etc/mqttwarn
26 |
27 | # Add user "mqttwarn"
28 | RUN groupadd -r mqttwarn && useradd -r -g mqttwarn mqttwarn
29 | RUN chown -R mqttwarn:mqttwarn /etc/mqttwarn
30 |
31 | # Install package.
32 | COPY . /src
33 | RUN --mount=type=cache,id=pip,target=/root/.cache/pip \
34 | true \
35 | && pip install --upgrade pip \
36 | && pip install --prefer-binary versioningit wheel \
37 | && pip install --use-pep517 --prefer-binary '/src[all]'
38 |
39 | # Uninstall build prerequisites again.
40 | RUN apt-get --yes remove --purge git build-essential libmariadb-dev pkg-config && apt-get --yes autoremove
41 |
42 | # Purge /src and /tmp directories.
43 | RUN rm -rf /src /tmp/*
44 |
45 |
46 | # =======
47 | # Runtime
48 | # =======
49 |
50 | # Make process run as "mqttwarn" user
51 | USER mqttwarn
52 |
53 | # Use configuration file from host
54 | VOLUME ["/etc/mqttwarn"]
55 |
56 | # Set default configuration path
57 | ENV MQTTWARNINI="/etc/mqttwarn/mqttwarn.ini"
58 |
59 | # Invoke program
60 | CMD mqttwarn
61 |
--------------------------------------------------------------------------------
/Dockerfile.mqttwarn-slack:
--------------------------------------------------------------------------------
1 | # Docker build file for mqttwarn, with Slack SDK.
2 | #
3 | # Invoke like:
4 | #
5 | # docker build --tag=mqttwarn-slack --file=Dockerfile.mqttwarn-slack .
6 | #
7 |
8 | # Derive from upstream image.
9 | FROM ghcr.io/mqtt-tools/mqttwarn-standard:latest
10 |
11 | # Make package installation run as "root" user
12 | USER root
13 |
14 | # Install Slack SDK.
15 | RUN pip install wheel
16 | RUN pip install mqttwarn[slack]
17 |
18 | # Make process run as "mqttwarn" user
19 | USER mqttwarn
20 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include setup.cfg *.txt *.rst *.md
2 | exclude .bumpversion.cfg
3 | recursive-include mqttwarn *.ini *.py
4 | prune examples
5 | prune vendor
6 | prune templates
7 | prune tests
8 |
9 | # Documentation
10 | recursive-include docs *
11 | prune docs/_build
12 | global-exclude */__pycache__/*
13 | global-exclude *.pyc
14 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # ============
2 | # Main targets
3 | # ============
4 |
5 |
6 | # -------------
7 | # Configuration
8 | # -------------
9 |
10 | $(eval venvpath := .venv)
11 | $(eval pip := $(venvpath)/bin/pip)
12 | $(eval python := $(venvpath)/bin/python)
13 | $(eval pytest := $(venvpath)/bin/pytest)
14 | $(eval twine := $(venvpath)/bin/twine)
15 | $(eval sphinx := $(venvpath)/bin/sphinx-build)
16 | $(eval sphinx-autobuild := $(venvpath)/bin/sphinx-autobuild)
17 | $(eval isort := $(venvpath)/bin/isort)
18 | $(eval black := $(venvpath)/bin/black)
19 | $(eval poe := $(venvpath)/bin/poe)
20 |
21 | # Setup Python virtualenv
22 | setup-virtualenv:
23 | @test -e $(python) || python3 -m venv $(venvpath)
24 | $(pip) install versioningit
25 |
26 |
27 | # -------
28 | # Testing
29 | # -------
30 |
31 | # Run the main test suite
32 | test: install-tests
33 | @$(poe) test
34 |
35 | test-refresh: install-tests test
36 |
37 | test-junit: install-tests
38 | @$(pytest) -vvv tests --junit-xml .pytest_results/pytest.xml
39 |
40 | test-coverage: install-tests
41 | @$(pytest) --cov-report html:.pytest_results/htmlcov
42 |
43 |
44 | # ----------------------
45 | # Linting and Formatting
46 | # ----------------------
47 | format: install-tests
48 | $(poe) format
49 |
50 |
51 | # -------
52 | # Release
53 | # -------
54 |
55 | # Release this piece of software
56 | release:
57 | poe release
58 |
59 |
60 | # -------------
61 | # Documentation
62 | # -------------
63 |
64 | # Build the documentation
65 | docs-html: install-doctools
66 | cd docs; make html
67 |
68 | docs-serve:
69 | cd docs/_build/html; python3 -m http.server
70 |
71 | docs-autobuild: install-doctools
72 | $(sphinx-autobuild) --open-browser docs docs/_build
73 |
74 |
75 | # ===============
76 | # Utility targets
77 | # ===============
78 | install-doctools:
79 | @test -e $(python) || python3 -m venv $(venvpath)
80 | $(pip) install --quiet --upgrade --requirement=docs/requirements.txt
81 |
82 | install-tests: setup-virtualenv
83 | @test -e $(pytest) || $(pip) install --editable=.[test,develop] --upgrade
84 | @touch $(venvpath)/bin/activate
85 | @mkdir -p .pytest_results
86 |
--------------------------------------------------------------------------------
/assets/apns.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mqtt-tools/mqttwarn/d9d312df66b428bf36c053ebaa88e6de18f8aa37/assets/apns.png
--------------------------------------------------------------------------------
/assets/desktopnotify.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mqtt-tools/mqttwarn/d9d312df66b428bf36c053ebaa88e6de18f8aa37/assets/desktopnotify.jpg
--------------------------------------------------------------------------------
/assets/google-definition.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mqtt-tools/mqttwarn/d9d312df66b428bf36c053ebaa88e6de18f8aa37/assets/google-definition.jpg
--------------------------------------------------------------------------------
/assets/gss.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mqtt-tools/mqttwarn/d9d312df66b428bf36c053ebaa88e6de18f8aa37/assets/gss.png
--------------------------------------------------------------------------------
/assets/hipchat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mqtt-tools/mqttwarn/d9d312df66b428bf36c053ebaa88e6de18f8aa37/assets/hipchat.png
--------------------------------------------------------------------------------
/assets/icinga.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mqtt-tools/mqttwarn/d9d312df66b428bf36c053ebaa88e6de18f8aa37/assets/icinga.jpg
--------------------------------------------------------------------------------
/assets/ionic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mqtt-tools/mqttwarn/d9d312df66b428bf36c053ebaa88e6de18f8aa37/assets/ionic.png
--------------------------------------------------------------------------------
/assets/irccat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mqtt-tools/mqttwarn/d9d312df66b428bf36c053ebaa88e6de18f8aa37/assets/irccat.png
--------------------------------------------------------------------------------
/assets/linuxnotify.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mqtt-tools/mqttwarn/d9d312df66b428bf36c053ebaa88e6de18f8aa37/assets/linuxnotify.png
--------------------------------------------------------------------------------
/assets/logo/img/hz_wordmark_blk_200x75.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mqtt-tools/mqttwarn/d9d312df66b428bf36c053ebaa88e6de18f8aa37/assets/logo/img/hz_wordmark_blk_200x75.png
--------------------------------------------------------------------------------
/assets/logo/img/hz_wordmark_blk_500x187.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mqtt-tools/mqttwarn/d9d312df66b428bf36c053ebaa88e6de18f8aa37/assets/logo/img/hz_wordmark_blk_500x187.png
--------------------------------------------------------------------------------
/assets/logo/img/hz_wordmark_wht_200x75.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mqtt-tools/mqttwarn/d9d312df66b428bf36c053ebaa88e6de18f8aa37/assets/logo/img/hz_wordmark_wht_200x75.png
--------------------------------------------------------------------------------
/assets/logo/img/hz_wordmark_wht_500x187.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mqtt-tools/mqttwarn/d9d312df66b428bf36c053ebaa88e6de18f8aa37/assets/logo/img/hz_wordmark_wht_500x187.png
--------------------------------------------------------------------------------
/assets/logo/img/markonly_200x200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mqtt-tools/mqttwarn/d9d312df66b428bf36c053ebaa88e6de18f8aa37/assets/logo/img/markonly_200x200.png
--------------------------------------------------------------------------------
/assets/logo/img/markonly_500x500.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mqtt-tools/mqttwarn/d9d312df66b428bf36c053ebaa88e6de18f8aa37/assets/logo/img/markonly_500x500.png
--------------------------------------------------------------------------------
/assets/logo/img/markword_200x200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mqtt-tools/mqttwarn/d9d312df66b428bf36c053ebaa88e6de18f8aa37/assets/logo/img/markword_200x200.png
--------------------------------------------------------------------------------
/assets/logo/img/markword_500x500.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mqtt-tools/mqttwarn/d9d312df66b428bf36c053ebaa88e6de18f8aa37/assets/logo/img/markword_500x500.png
--------------------------------------------------------------------------------
/assets/mattermost.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mqtt-tools/mqttwarn/d9d312df66b428bf36c053ebaa88e6de18f8aa37/assets/mattermost.png
--------------------------------------------------------------------------------
/assets/mqttwarn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mqtt-tools/mqttwarn/d9d312df66b428bf36c053ebaa88e6de18f8aa37/assets/mqttwarn.png
--------------------------------------------------------------------------------
/assets/pastebin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mqtt-tools/mqttwarn/d9d312df66b428bf36c053ebaa88e6de18f8aa37/assets/pastebin.png
--------------------------------------------------------------------------------
/assets/prowl.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mqtt-tools/mqttwarn/d9d312df66b428bf36c053ebaa88e6de18f8aa37/assets/prowl.jpg
--------------------------------------------------------------------------------
/assets/pushalot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mqtt-tools/mqttwarn/d9d312df66b428bf36c053ebaa88e6de18f8aa37/assets/pushalot.png
--------------------------------------------------------------------------------
/assets/pushbullet.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mqtt-tools/mqttwarn/d9d312df66b428bf36c053ebaa88e6de18f8aa37/assets/pushbullet.jpg
--------------------------------------------------------------------------------
/assets/pushover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mqtt-tools/mqttwarn/d9d312df66b428bf36c053ebaa88e6de18f8aa37/assets/pushover.png
--------------------------------------------------------------------------------
/assets/pushsafer.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mqtt-tools/mqttwarn/d9d312df66b428bf36c053ebaa88e6de18f8aa37/assets/pushsafer.jpg
--------------------------------------------------------------------------------
/assets/slack.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mqtt-tools/mqttwarn/d9d312df66b428bf36c053ebaa88e6de18f8aa37/assets/slack.png
--------------------------------------------------------------------------------
/assets/telegram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mqtt-tools/mqttwarn/d9d312df66b428bf36c053ebaa88e6de18f8aa37/assets/telegram.png
--------------------------------------------------------------------------------
/assets/tootpaste.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mqtt-tools/mqttwarn/d9d312df66b428bf36c053ebaa88e6de18f8aa37/assets/tootpaste.png
--------------------------------------------------------------------------------
/assets/twilio.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mqtt-tools/mqttwarn/d9d312df66b428bf36c053ebaa88e6de18f8aa37/assets/twilio.jpg
--------------------------------------------------------------------------------
/assets/twitter.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mqtt-tools/mqttwarn/d9d312df66b428bf36c053ebaa88e6de18f8aa37/assets/twitter.jpg
--------------------------------------------------------------------------------
/assets/zabbix.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mqtt-tools/mqttwarn/d9d312df66b428bf36c053ebaa88e6de18f8aa37/assets/zabbix.png
--------------------------------------------------------------------------------
/codecov.yml:
--------------------------------------------------------------------------------
1 | # https://docs.codecov.io/docs/common-recipe-list
2 | # https://docs.codecov.io/docs/commit-status#patch-status
3 |
4 | coverage:
5 | status:
6 | project:
7 | default:
8 | target: auto # the required coverage value
9 | threshold: 4% # the leniency in hitting the target
10 | patch:
11 | default:
12 | target: 0%
13 | informational: true
14 |
--------------------------------------------------------------------------------
/contrib/amqp-puka-get.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | from __future__ import print_function
4 | import sys
5 | import puka
6 | import json
7 |
8 | exchange = 'mqttwarn'
9 | routing_key = 'all'
10 |
11 | client = puka.Client('amqp://guest:guest@localhost')
12 | promise = client.connect()
13 | client.wait(promise)
14 |
15 | promise = client.exchange_declare(exchange=exchange, type='direct')
16 | client.wait(promise)
17 |
18 | promise = client.queue_declare(exclusive=True)
19 | queue_name = client.wait(promise)['queue']
20 |
21 | promise = client.queue_bind(exchange=exchange, queue=queue_name, routing_key=routing_key)
22 | client.wait(promise)
23 |
24 | consume = client.basic_consume(queue=queue_name, no_ack=False)
25 | while True:
26 | try:
27 | msg = client.wait(consume)
28 | print(json.dumps(msg, indent=4))
29 | client.basic_ack(msg)
30 | except KeyboardInterrupt:
31 | client.close()
32 | sys.exit(0)
33 |
34 |
--------------------------------------------------------------------------------
/contrib/zabbix_mqtt_agent.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | # Example "agent" for Zabbix/mqttwarn which publishes two metrics
5 | # every few seconds.
6 |
7 | import paho.mqtt.client as paho # pip install paho-mqtt
8 | import ssl
9 | import time
10 | import sys
11 | from random import randint
12 |
13 | __author__ = 'Jan-Piet Mens '
14 | __copyright__ = 'Copyright 2014 Jan-Piet Mens'
15 | __license__ = """Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)"""
16 |
17 | CLIENT = 'jog09'
18 | HOST_TOPIC = "zabbix/clients/%s" % CLIENT
19 |
20 | mqttc = paho.Client(clean_session=True, userdata=None)
21 |
22 | def metric(name, value):
23 | mqttc.publish("zabbix/item/%s/%s" % (CLIENT, name), value)
24 | mqttc.loop()
25 |
26 | mqttc.tls_set('/Users/jpm/tmp/mqtt/root.ca',
27 | tls_version=ssl.PROTOCOL_TLSv1)
28 |
29 | mqttc.tls_insecure_set(True) # Ensure False in production
30 |
31 | # If this client dies, ensure broker publishes our death on our behalf (LWT)
32 | mqttc.will_set(HOST_TOPIC, payload="0", qos=0, retain=True)
33 |
34 | # mqttc.username_pw_set('john', 'secret')
35 | mqttc.connect("localhost", 8883, 60)
36 |
37 | # Indicate host is up
38 | mqttc.publish(HOST_TOPIC, "1")
39 | rc = 0
40 | while rc == 0:
41 | try:
42 | rc = mqttc.loop()
43 |
44 | metric('system.cpu.load', randint(2, 8))
45 | metric('time.stamp', time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
46 |
47 | time.sleep(10)
48 | except KeyboardInterrupt:
49 | sys.exit(0)
50 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | # To use this Docker Compose file for mqttwarn:
2 | #
3 | # 1. Acquire "docker-compose.yml":
4 | # wget https://raw.githubusercontent.com/mqtt-tools/mqttwarn/main/docker-compose.yml
5 | #
6 | # 2. Create a directory for mqttwarn to store "mqttwarn.ini" and "funcs.py" in:
7 | # mkdir /path/to/mqttwarn
8 | #
9 | # 3. Acquire "mqttwarn.ini":
10 | # wget https://raw.githubusercontent.com/mqtt-tools/mqttwarn/main/mqttwarn/examples/basic/mqttwarn.ini -O /path/to/mqtwarn/mqttwarn.ini
11 | #
12 | # 4. Define the location to the custom functions file within "mqttwarn.ini":
13 | #
14 | # functions = 'funcs.py'
15 | version: '3'
16 |
17 | services:
18 | mqttwarn:
19 | image: ghcr.io/mqtt-tools/mqttwarn-full:latest
20 | container_name: mqttwarn
21 | restart: always
22 | volumes:
23 | - /path/to/mqttwarn:/etc/mqttwarn
24 | - /etc/localtime:/etc/localtime:ro
25 | # If you want to change the default location of mqttwarn.ini, uncomment the following lines:
26 | #environment:
27 | # - MQTTWARNINI=/etc/mqttwarn/mqttwarn.ini
28 |
--------------------------------------------------------------------------------
/docs/.gitignore:
--------------------------------------------------------------------------------
1 | _build
2 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Minimal makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line, and also
5 | # from the environment for the first two.
6 | SPHINXOPTS ?=
7 | SPHINXBUILD ?= sphinx-build
8 | SOURCEDIR = .
9 | BUILDDIR = _build
10 |
11 | # Put it first so that "make" without argument is like "make help".
12 | help:
13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14 |
15 | .PHONY: help Makefile
16 |
17 | # Catch-all target: route all unknown targets to Sphinx using the new
18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
19 | %: Makefile
20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
21 |
--------------------------------------------------------------------------------
/docs/assets:
--------------------------------------------------------------------------------
1 | ../assets
--------------------------------------------------------------------------------
/docs/conf.py:
--------------------------------------------------------------------------------
1 | # Configuration file for the Sphinx documentation builder.
2 | #
3 | # For the full list of built-in configuration values, see the documentation:
4 | # https://www.sphinx-doc.org/en/master/usage/configuration.html
5 |
6 | # -- Project information -----------------------------------------------------
7 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
8 |
9 | project = "mqttwarn"
10 | copyright = "2014-2023, Jan-Piet Mens, Ben Jones, Andreas Motl"
11 | author = "Jan-Piet Mens, Ben Jones, Andreas Motl"
12 |
13 | # -- General configuration ---------------------------------------------------
14 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
15 |
16 | extensions = [
17 | "myst_parser",
18 | "sphinx_copybutton",
19 | "sphinx_togglebutton",
20 | "sphinx.ext.intersphinx",
21 | "sphinx.ext.todo",
22 | "sphinx.ext.ifconfig",
23 | "sphinxext.opengraph",
24 | ]
25 |
26 |
27 | templates_path = ["_templates"]
28 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
29 |
30 |
31 | # -- Options for HTML output -------------------------------------------------
32 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
33 |
34 | html_theme = "furo"
35 | html_static_path = ["_static"]
36 | html_logo = "mqttwarn-logo.png"
37 | html_show_sourcelink = True
38 |
39 |
40 | # == Extension configuration ==========================================
41 |
42 | todo_include_todos = True
43 | intersphinx_mapping = {
44 | "python": ("https://docs.python.org/3", None),
45 | "sphinx": ("https://www.sphinx-doc.org/en/master/", None),
46 | "myst": ("https://myst-parser.readthedocs.io/en/latest", None),
47 | }
48 | linkcheck_ignore = [r"https://www.researchgate.net/publication/.*"]
49 | sphinx_tabs_valid_builders = ["linkcheck"]
50 |
51 |
52 | # -- Options for MyST -------------------------------------------------
53 | myst_heading_anchors = 3
54 | myst_enable_extensions = [
55 | "attrs_block",
56 | "attrs_inline",
57 | "colon_fence",
58 | "deflist",
59 | "fieldlist",
60 | "linkify",
61 | "strikethrough",
62 | "tasklist",
63 | ]
64 |
65 | # -- Options for sphinx-copybutton ------------------------------------
66 | copybutton_remove_prompts = True
67 | copybutton_line_continuation_character = "\\"
68 | copybutton_prompt_text = r">>> |\.\.\. |\$ |sh\$ |PS> |cr> |mysql> |In \[\d*\]: | {2,5}\.\.\.: | {5,8}: "
69 | copybutton_prompt_is_regexp = True
70 |
71 | # -- Options for sphinxext-opengraph ----------------------------------
72 | ogp_site_url = "https://mqttwarn.readthedocs.io/"
73 | ogp_image = "https://mqttwarn.readthedocs.io/en/latest/_static/mqttwarn-logo.png"
74 | ogp_description_length = 300
75 | ogp_enable_meta_description = True
76 |
--------------------------------------------------------------------------------
/docs/configure/index.rst:
--------------------------------------------------------------------------------
1 | .. _configure:
2 |
3 | #############
4 | Configuration
5 | #############
6 |
7 | This part of the documentation covers the configuration of mqttwarn. The second
8 | step to using any software package after its :ref:`installation `,
9 | is getting it properly configured. Please read this section carefully.
10 |
11 | To directly jump to the corresponding sections, visit
12 | :ref:`mqttwarn.ini`, :ref:`services`, :ref:`topics`, and :ref:`transformations`.
13 |
14 |
15 | ******************************
16 | Application configuration file
17 | ******************************
18 |
19 | In this section, you will learn about the layout, structure, and semantics of
20 | the application configuration file ``mqttwarn.ini``.
21 | mqttwarn needs it properly configured in order to operate successfully.
22 |
23 |
24 | Create starter files
25 | ====================
26 |
27 |
28 | Blueprints for mqttwarn configuration files are available within the mqttwarn
29 | repository at `mqttwarn.ini`_ and `udf.py`_. You can use them as personal
30 | starter files, and edit them to your taste::
31 |
32 | # Create configuration file.
33 | mqttwarn make-config > mqttwarn.ini
34 |
35 | # Create file hosting user-defined functions.
36 | mqttwarn make-udf > udf.py
37 |
38 | .. important::
39 |
40 | If you are using PowerShell on Windows 10, you may find the files to be written
41 | using the ``UTF-16`` charset encoding. However, ``mqttwarn`` works with ``UTF-8``.
42 | In order to switch to ``UTF-8``, please invoke this command beforehand.
43 |
44 | .. code-block:: powershell
45 |
46 | $PSDefaultParameterValues['Out-File:Encoding'] = 'utf8'
47 |
48 |
49 | Learn and edit configuration
50 | ============================
51 |
52 | In order to implement mqttwarn for your use case, you will need to edit its
53 | configuration files according to your needs.
54 |
55 | To learn about the application configuration file ``mqttwarn.ini``, please
56 | follow up reading the following sections of the documentation.
57 |
58 | .. toctree::
59 | :maxdepth: 1
60 |
61 | mqttwarn.ini
62 | service
63 | topic
64 | transformation
65 | task
66 |
67 | Notification "services" define where messages are routed to, "topics" are
68 | definitions of MQTT topic subscriptions, and with "transformations", you are
69 | defining how messages will be filtered, decoded, and re-formatted while
70 | mqttwarn is processing them.
71 |
72 |
73 | .. _mqttwarn.ini: https://github.com/mqtt-tools/mqttwarn/blob/main/mqttwarn/examples/basic/mqttwarn.ini
74 | .. _udf.py: https://github.com/mqtt-tools/mqttwarn/blob/main/mqttwarn/examples/basic/udf.py
75 |
--------------------------------------------------------------------------------
/docs/configure/task.md:
--------------------------------------------------------------------------------
1 | (task)=
2 | (tasks)=
3 | # Tasks
4 |
5 |
6 | ## Periodic tasks
7 |
8 | _mqttwarn_ can use functions you define in the file specified `[defaults]` section
9 | to periodically do whatever you want, for example, publish an MQTT message. There
10 | are two things you have to do:
11 |
12 | 1. Create the function
13 | 2. Configure _mqttwarn_ to use that function and specify the interval in seconds
14 |
15 | Assume we have the following user-defined function.
16 | ```python
17 | from mqttwarn.model import Service
18 |
19 | def pinger(srv: Service):
20 | srv.mqttc.publish("pt/PINGER", "Hello from mqttwarn!", qos=0)
21 | ```
22 |
23 | We configure this function to run every, say, 10 seconds, in the `mqttwarn.ini`,
24 | in the `[cron]` section:
25 |
26 | ```ini
27 | [cron]
28 | pinger = 10.5
29 | ```
30 |
31 | Each keyword in the `[cron]` section specifies the name of one of your custom
32 | functions, and its float value is an interval in _seconds_ after which your
33 | user-defined function, in this case `pinger()`, is invoked. Your function has
34 | access to the `srv` object described above.
35 |
36 | Function names are to be specified in lower-case characters.
37 |
38 | If you want to run the user-defined function immediately after starting mqttwarn
39 | instead of waiting for the interval to elapse, you might want to add `now=true`.
40 | ```ini
41 | [cron]
42 | pinger = 10.5; now=true
43 | ```
44 |
--------------------------------------------------------------------------------
/docs/examples:
--------------------------------------------------------------------------------
1 | ../examples
--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | pushd %~dp0
4 |
5 | REM Command file for Sphinx documentation
6 |
7 | if "%SPHINXBUILD%" == "" (
8 | set SPHINXBUILD=sphinx-build
9 | )
10 | set SOURCEDIR=.
11 | set BUILDDIR=_build
12 |
13 | %SPHINXBUILD% >NUL 2>NUL
14 | if errorlevel 9009 (
15 | echo.
16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
17 | echo.installed, then set the SPHINXBUILD environment variable to point
18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
19 | echo.may add the Sphinx directory to PATH.
20 | echo.
21 | echo.If you don't have Sphinx installed, grab it from
22 | echo.https://www.sphinx-doc.org/
23 | exit /b 1
24 | )
25 |
26 | if "%1" == "" goto help
27 |
28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
29 | goto end
30 |
31 | :help
32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
33 |
34 | :end
35 | popd
36 |
--------------------------------------------------------------------------------
/docs/mqttwarn-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mqtt-tools/mqttwarn/d9d312df66b428bf36c053ebaa88e6de18f8aa37/docs/mqttwarn-logo.png
--------------------------------------------------------------------------------
/docs/readme.rst:
--------------------------------------------------------------------------------
1 | ../README.rst
--------------------------------------------------------------------------------
/docs/requirements.txt:
--------------------------------------------------------------------------------
1 | furo
2 | jinja2<4
3 | myst-parser[linkify]<2
4 | sphinx<7
5 | sphinx-copybutton<1
6 | sphinx-togglebutton<1
7 | sphinxext-opengraph<1
8 |
--------------------------------------------------------------------------------
/docs/usage/freebsd.md:
--------------------------------------------------------------------------------
1 | (using-freebsd)=
2 | # Installing mqttwarn on FreeBSD
3 |
4 | With this installation method about how to install mqttwarn on [FreeBSD],
5 | you will acquire the [py-mqttwarn] package from the [FreeBSD ports tree],
6 | and install it on your machine.
7 |
8 | ## Synopsis
9 |
10 | ```bash
11 | pkg install sysutils/py-mqttwarn
12 | ```
13 |
14 |
15 | ## Additional service plugins
16 |
17 | In order to add support for a specific service plugin not bundled with the
18 | default installation, you will need to install its dependencies manually.
19 |
20 | In order to do that, head over to the [`setup.py`] file of mqttwarn, inspect
21 | the list of dependencies, and use the [FreshPorts search] to figure out the
22 | package name of the corresponding dependency on the FreeBSD ports tree,
23 | for example [py-pyserial] or [py-slixmpp]. Then, install the additional
24 | package(s) using [pkg], for example:
25 | ```
26 | pkg install comms/py-pyserial
27 | pkg install net-im/py-slixmpp
28 | ```
29 |
30 | If some package is not available there, you may consider installing it from
31 | the [Python package index] using `pip install `, for example:
32 | ```
33 | pip install pyserial slixmpp
34 | ```
35 |
36 |
37 | [FreeBSD]: https://www.freebsd.org/
38 | [FreeBSD ports tree]: https://www.freebsd.org/ports/
39 | [FreshPorts search]: https://www.freshports.org/search.php
40 | [pkg]: https://man.freebsd.org/cgi/man.cgi?query=pkg
41 | [py-mqttwarn]: https://www.freshports.org/sysutils/py-mqttwarn/
42 | [py-pyserial]: https://www.freshports.org/comms/py-pyserial/
43 | [py-slixmpp]: https://www.freshports.org/net-im/py-slixmpp/
44 | [Python package index]: https://pypi.org/
45 | [`setup.py`]: https://github.com/mqtt-tools/mqttwarn/blob/main/setup.py
46 |
--------------------------------------------------------------------------------
/docs/usage/index.rst:
--------------------------------------------------------------------------------
1 | .. _installation:
2 | .. _installing:
3 | .. _install:
4 | .. _usage:
5 | .. _using:
6 | .. _use:
7 |
8 | ######################
9 | Installation and usage
10 | ######################
11 |
12 | This part of the documentation covers the installation and usage of mqttwarn.
13 | The first step to using any software package is getting it properly installed.
14 | Please read this section carefully.
15 |
16 | After successfully installing the software, please follow up to learn about how
17 | to :ref:`configure ` it.
18 |
19 | Installation variants
20 | =====================
21 |
22 | mqttwarn can be installed natively on your system, or by running an OCI container
23 | image on Docker, Podman, Kubernetes, or friends. Depending on your preferences or
24 | system environment, please use either of those variants:
25 |
26 | - :ref:`using-pip`
27 | - :ref:`using-oci-image`
28 | - :ref:`using-freebsd`
29 |
30 | If you are interested in contributing to mqttwarn, you should setup a development
31 | sandbox, see :ref:`sandbox`.
32 |
33 | Configuration file
34 | ==================
35 |
36 | Before running mqttwarn, you will need a configuration file.
37 |
38 | The path to the configuration file is obtained from the ``MQTTWARNINI`` environment
39 | variable, and defaults to ``mqttwarn.ini`` in the current directory.
40 | On server installations, the default configuration file is located at
41 | ``/etc/mqttwarn/mqttwarn.ini``.
42 |
43 | You can create a configuration file blueprint easily::
44 |
45 | mqttwarn make-config
46 |
47 |
48 | Running
49 | =======
50 |
51 | In order to start the program, just type::
52 |
53 | mqttwarn
54 |
55 | or::
56 |
57 | MQTTWARNINI=/path/to/mqttwarn.ini mqttwarn
58 |
59 |
60 |
61 | .. toctree::
62 | :hidden:
63 |
64 | pip
65 | oci
66 | freebsd
67 | standalone
68 |
--------------------------------------------------------------------------------
/docs/usage/pip.md:
--------------------------------------------------------------------------------
1 | (using-pip)=
2 | # Installing mqttwarn with pip
3 |
4 | With this installation method, you will acquire a package of mqttwarn from PyPI
5 | and install it on your workstation. We recommend to use a Python virtualenv for
6 | that.
7 |
8 | ## Synopsis
9 |
10 | ```bash
11 | pip install --upgrade mqttwarn
12 | ```
13 |
14 | You can also add support for a specific service plugin.
15 |
16 | ```bash
17 | pip install --upgrade 'mqttwarn[xmpp]'
18 | ```
19 |
20 | You can also add support for multiple services, all at once.
21 |
22 | ```bash
23 | pip install --upgrade 'mqttwarn[apprise,asterisk,nsca,desktopnotify,tootpaste,xmpp]'
24 | ```
25 |
--------------------------------------------------------------------------------
/docs/workbench/changelog.rst:
--------------------------------------------------------------------------------
1 | ../../CHANGES.rst
--------------------------------------------------------------------------------
/etc/OpenWRT.init:
--------------------------------------------------------------------------------
1 | #!/bin/sh /etc/rc.common
2 | # Script to start mqttwarn as a daemon for OpenWRT
3 |
4 | START=95
5 | STOP=10
6 |
7 | DIR="/overlay/mosquitto/"
8 | BIN="/overlay/mosquitto/mqttwarn"
9 | PIDFILE=/var/run/mqttwarn.pid
10 |
11 | start() {
12 | echo start
13 | cd $DIR
14 | start-stop-daemon -b -S -q -m -p $PIDFILE -x $BIN
15 | }
16 |
17 | stop() {
18 | echo stop
19 | start-stop-daemon -K -q -p $PIDFILE
20 | rm -f $PIDFILE
21 | }
22 |
--------------------------------------------------------------------------------
/etc/mqttwarn.default:
--------------------------------------------------------------------------------
1 | # For systemd-based systems (used by mqttwarn.service)
2 | MQTTWARNINI="/etc/mqttwarn/mqttwarn.ini"
3 | MQTTWARN_OPTIONS=""
4 |
5 | # For init-based systems (used by mqttwarn.init)
6 | START_DAEMON=true
7 | VERBOSE=true
8 |
--------------------------------------------------------------------------------
/etc/mqttwarn.logrotate:
--------------------------------------------------------------------------------
1 | /var/log/mqttwarn/*.log {
2 | rotate 7
3 | daily
4 | compress
5 | size 2M
6 | nocreate
7 | missingok
8 | postrotate
9 | /bin/systemctl restart mqttwarn
10 | endscript
11 | }
12 |
--------------------------------------------------------------------------------
/etc/mqttwarn.openrc:
--------------------------------------------------------------------------------
1 | #!/sbin/openrc-run
2 |
3 | command="/usr/local/bin/mqttwarn"
4 | command_args="${MQTTWARN_OPTIONS}"
5 | command_background=yes
6 | pidfile=/run/mqttwarn.pid
7 |
8 | name="mqttwarn"
9 | description="mqttwarn pluggable mqtt notification service"
10 |
11 | depend() {
12 | need net
13 | }
14 |
--------------------------------------------------------------------------------
/etc/mqttwarn.service:
--------------------------------------------------------------------------------
1 | # ----------------------------------------------
2 | # systemd unit configuration file for mqttwarn
3 | # ----------------------------------------------
4 | #
5 | # Intro
6 | # -----
7 | # This systemd script assumes you installed mqttwarn
8 | # using `pip install mqttwarn`.
9 | #
10 | # Setup
11 | # -----
12 | #
13 | # Prepare and enable the systemd service::
14 | #
15 | # useradd --create-home --shell /bin/bash mqttwarn
16 | # cp etc/mqttwarn.default /etc/default/mqttwarn
17 | # cp etc/mqttwarn.service /usr/lib/systemd/system/
18 | # cp etc/mqttwarn.logrotate /etc/logrotate.d/mqttwarn
19 | # mkdir /var/log/mqttwarn
20 | # chown mqttwarn:mqttwarn /var/log/mqttwarn
21 | # systemctl enable mqttwarn
22 | #
23 | # Configuration
24 | # -------------
25 | # The configuration file is located at /etc/mqttwarn/mqttwarn.ini,
26 | # but the default setting can be changed by amending the
27 | # MQTTWARNINI environment variable defined in /etc/default/mqttwarn.
28 | #
29 | # Setup example configuration::
30 | #
31 | # mkdir /etc/mqttwarn
32 | # cp mqttwarn.ini.sample /etc/mqttwarn/mqttwarn.ini
33 | #
34 | # Start
35 | # -----
36 | # ::
37 | #
38 | # systemctl start mqttwarn
39 | #
40 |
41 | [Unit]
42 | Description=mqttwarn pluggable mqtt notification service
43 | Documentation=https://github.com/mqtt-tools/mqttwarn
44 | After=network.target
45 |
46 | [Service]
47 | Type=simple
48 | User=mqttwarn
49 | Group=mqttwarn
50 | LimitNOFILE=65536
51 | Environment='STDOUT=/var/log/mqttwarn/mqttwarn.log'
52 | Environment='STDERR=/var/log/mqttwarn/mqttwarn.log'
53 | EnvironmentFile=/etc/default/mqttwarn
54 | PassEnvironment=MQTTWARNINI
55 | ExecStart=/bin/sh -c 'exec /usr/local/bin/mqttwarn ${MQTTWARN_OPTIONS} >>${STDOUT} 2>>${STDERR}'
56 | KillMode=control-group
57 | Restart=on-failure
58 |
59 | [Install]
60 | WantedBy=multi-user.target
61 | Alias=mqttwarn.service
62 |
--------------------------------------------------------------------------------
/etc/supervisor.ini:
--------------------------------------------------------------------------------
1 | [program:mqttwarn]
2 | command = /usr/local/bin/mqttwarn
3 | user = mqttwarn
4 | environment = MQTTWARNINI="/path/to/mqttwarn.ini"
5 |
--------------------------------------------------------------------------------
/examples/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mqtt-tools/mqttwarn/d9d312df66b428bf36c053ebaa88e6de18f8aa37/examples/__init__.py
--------------------------------------------------------------------------------
/examples/alexa/alexa.ini:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | [defaults]
4 | hostname = 'localhost'
5 | port = 1883
6 | clientid = 'mqttwarn'
7 |
8 | logfile = 'stream://sys.stderr'
9 | ; one of: CRITICAL, DEBUG, ERROR, INFO, WARN
10 | loglevel = DEBUG
11 | ;logformat = '%(asctime)-15s %(levelname)-8s [%(name)-25s] %(message)s'
12 |
13 | launch = pipe
14 |
15 | [config:pipe]
16 | targets = {
17 | 'alexa_living_room' : [ '/home/pi/shell/alexa-remote-control/saystdin', '-d', 'Living_Room' ],
18 | 'everywhere_group' : [ '/home/pi/shell/alexa-remote-control/announce_stdin', '-d', 'Everywhere' ],
19 | }
20 |
21 | # echo testing from m q t t warn | mosquitto_pub -t 'alexa/living_room' -l
22 | [alexa/living_room]
23 | targets = pipe:alexa_living_room
24 |
25 | # echo Hello world | mosquitto_pub -t 'alexa/everywhere' -l
26 | [alexa/everywhere]
27 | targets = pipe:everywhere_group
28 |
--------------------------------------------------------------------------------
/examples/alexa/announce_stdin:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | . /home/pi/shell/alexa-remote-control/secrets.sh
4 |
5 | # Example:
6 | # echo test from command line | ./announce_stdin -d Living_Room
7 |
8 | read message
9 | echo ${message}
10 |
11 | env USE_ANNOUNCEMENT_FOR_SPEAK=1 /home/pi/shell/alexa-remote-control/alexa_remote_control.sh ${*} -e speak:"${message}"
12 |
--------------------------------------------------------------------------------
/examples/alexa/readme.md:
--------------------------------------------------------------------------------
1 | # Amazon Alexa
2 |
3 | ## About
4 |
5 | An alternative to alexa-notify-me notification (speaker glows yellow and awaits
6 | instruction to play the notification) is for TTS to specific devices or
7 | announce to a speaker group.
8 |
9 | See the examples directory for integration with pipe and the [alexa-remote-control]
10 | shell scripts.
11 |
12 | ## Instructions
13 |
14 | * Download or clone [alexa-remote-control].
15 | * Edit the [secrets.sh](./secrets.sh) file.
16 | * Ensure paths are correct.
17 | Scripts and `alexa.ini` file assume path `/home/pi/shell/alexa-remote-control`.
18 | * Edit `alexa.ini` file targets with device names and/or group name.
19 | `stdin` for single devices, `announce_stdin` for groups.
20 | * Sanity check, `chmod a+x` on all shell scripts.
21 |
22 |
23 | [alexa-remote-control]: https://github.com/thorsten-gehrig/alexa-remote-control
24 |
--------------------------------------------------------------------------------
/examples/alexa/saystdin:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | . /home/pi/shell/alexa-remote-control/secrets.sh
4 |
5 | # Example:
6 | # echo test from command line | ./saystdin -d Speaker_Group
7 |
8 | read message
9 | echo ${message}
10 |
11 | /home/pi/shell/alexa-remote-control/alexa_remote_control_plain.sh ${*} -e speak:"${message}"
12 |
--------------------------------------------------------------------------------
/examples/alexa/secrets.sh:
--------------------------------------------------------------------------------
1 | # EDIT this script! Add/remove/edit as needed
2 |
3 | export EMAIL='your@email.here.com'
4 | export PASSWORD='your passwod here'
5 | export MFA_SECRET='your 2fa scret here'
6 | export LANGUAGE='en-US'
7 | export TTS_LOCALE='en-EN'
8 | export AMAZON='amazon.com'
9 | export ALEXA='pitangui.amazon.com'
10 |
--------------------------------------------------------------------------------
/examples/arduino-temperature/readme.md:
--------------------------------------------------------------------------------
1 | # Arduino » Enrich temperature readings
2 |
3 |
4 | ## About
5 |
6 | Assuming we get, from an Arduino, say, a single numerical value in the payload
7 | of an MQTT message, we want to generate JSON with some additional fields. Using
8 | a [Jinja2] template for the task, does exactly what we need.
9 |
10 |
11 | ## Implementation
12 |
13 | The following target configuration invokes the template:
14 |
15 | ```ini
16 | [arduino/temp]
17 | targets = log:info, http:graylog2
18 | template = temp2json.json
19 | ```
20 |
21 | The Jinja2 template looks like this.
22 | ```jinja
23 | {#
24 | We expect a single numeric temperature value in `payload'.
25 | Return JSON suitable for Graylog2 (requires `host` and `short_message`).
26 |
27 | Define a data structure in Jinja2 and return it as a JSON string.
28 | Note how transformation data (produced within mqttwarn) is used:
29 | The variables `_dtiso` and `payload` carry the timestamp and the
30 | payload respectively.
31 | #}
32 | {% set data = {
33 | 'host': topic,
34 | 'short_message': "Heat " + payload,
35 | 'tst': _dtiso,
36 | 'temperature': payload,
37 | 'woohooo': 17,
38 | }
39 | %}
40 | {{ data | jsonify }}
41 | ```
42 |
43 | An example JSON string returned by that template is then passed to the
44 | configured targets.
45 | ```json
46 | {"host": "arduino/temp", "woohooo": 17, "tst": "2014-04-13T09:25:46.247150Z", "temperature": "22", "short_message": "Heat 22"}
47 | ```
48 |
49 |
50 | [Jinja2]: https://jinja.palletsprojects.com/templates/
51 |
--------------------------------------------------------------------------------
/examples/conftest.py:
--------------------------------------------------------------------------------
1 | from tests.fixtures.ntfy import ntfy_service # noqa:F401
2 |
--------------------------------------------------------------------------------
/examples/frigate/.env:
--------------------------------------------------------------------------------
1 | # Software component versions.
2 | MOSQUITTO_VERSION=2.0.15
3 | NTFY_VERSION=latest
4 |
5 | # Broker configuration (Mosquitto).
6 | PORT_MOSQUITTO=1883
7 |
8 | # Notification service configuration (ntfy).
9 | PORT_NTFY=5555
10 |
--------------------------------------------------------------------------------
/examples/frigate/.gitignore:
--------------------------------------------------------------------------------
1 | *.jpg
2 | *.jpeg
3 | *.png
4 |
--------------------------------------------------------------------------------
/examples/frigate/assets/frigate-event-end.json:
--------------------------------------------------------------------------------
1 | {
2 | "before": {
3 | "id": "1680791459.255384-abcdef",
4 | "camera": "cam-testdrive",
5 | "frame_time": 1680791459.255384,
6 | "snapshot_time": 0,
7 | "label": "goat",
8 | "sub_label": null,
9 | "top_score": 0,
10 | "false_positive": true,
11 | "start_time": 1680791459.255384,
12 | "end_time": null,
13 | "current_zones": [],
14 | "entered_zones": [],
15 | "has_clip": false,
16 | "has_snapshot": false
17 | },
18 | "after": {
19 | "id": "1680791459.255384-abcdef",
20 | "camera": "cam-testdrive",
21 | "frame_time": 1680791506.638857,
22 | "snapshot_time": 1680791506.638857,
23 | "label": "goat",
24 | "sub_label": null,
25 | "false_positive": false,
26 | "start_time": 1680791459.255384,
27 | "end_time": null,
28 | "current_zones": [],
29 | "entered_zones": [
30 | "zone1"
31 | ],
32 | "has_clip": true,
33 | "has_snapshot": true
34 | },
35 | "type": "end"
36 | }
37 |
--------------------------------------------------------------------------------
/examples/frigate/assets/frigate-event-false-positive.json:
--------------------------------------------------------------------------------
1 | {
2 | "before": {
3 | "id": "1680791459.255384-abcdef",
4 | "camera": "cam-testdrive",
5 | "frame_time": 1680791459.255384,
6 | "snapshot_time": 0,
7 | "label": "goat",
8 | "sub_label": null,
9 | "top_score": 0,
10 | "false_positive": true,
11 | "start_time": 1680791459.255384,
12 | "end_time": null,
13 | "current_zones": [],
14 | "entered_zones": [],
15 | "has_clip": false,
16 | "has_snapshot": false
17 | },
18 | "after": {
19 | "id": "1680791459.255384-abcdef",
20 | "camera": "cam-testdrive",
21 | "frame_time": 1680791506.638857,
22 | "snapshot_time": 1680791506.638857,
23 | "label": "goat",
24 | "sub_label": null,
25 | "false_positive": true,
26 | "start_time": 1680791459.255384,
27 | "end_time": null,
28 | "current_zones": [],
29 | "entered_zones": [
30 | "zone1"
31 | ],
32 | "has_clip": true,
33 | "has_snapshot": true
34 | },
35 | "type": "new"
36 | }
37 |
--------------------------------------------------------------------------------
/examples/frigate/assets/frigate-event-full.json:
--------------------------------------------------------------------------------
1 | {
2 | "before": {
3 | "id": "1680791459.255384-abcdef",
4 | "camera": "cam-testdrive",
5 | "frame_time": 1680791459.255384,
6 | "snapshot_time": 0,
7 | "label": "goat",
8 | "sub_label": null,
9 | "top_score": 0,
10 | "false_positive": true,
11 | "start_time": 1680791459.255384,
12 | "end_time": null,
13 | "score": 0.7,
14 | "box": [
15 | 0,
16 | 20,
17 | 0,
18 | 20
19 | ],
20 | "area": 400,
21 | "ratio": 1,
22 | "region": [
23 | 0,
24 | 0,
25 | 320,
26 | 320
27 | ],
28 | "stationary": false,
29 | "motionless_count": 0,
30 | "position_changes": 0,
31 | "current_zones": [],
32 | "entered_zones": [],
33 | "has_clip": false,
34 | "has_snapshot": false
35 | },
36 | "after": {
37 | "id": "1680791459.255384-abcdef",
38 | "camera": "cam-testdrive",
39 | "frame_time": 1680791506.638857,
40 | "snapshot_time": 1680791506.638857,
41 | "label": "goat",
42 | "sub_label": null,
43 | "top_score": 0.75,
44 | "false_positive": false,
45 | "start_time": 1680791459.255384,
46 | "end_time": null,
47 | "score": 0.8,
48 | "box": [
49 | 1,
50 | 21,
51 | 1,
52 | 21
53 | ],
54 | "area": 400,
55 | "ratio": 1,
56 | "region": [
57 | 0,
58 | 0,
59 | 320,
60 | 320
61 | ],
62 | "stationary": false,
63 | "motionless_count": 1,
64 | "position_changes": 2,
65 | "current_zones": [
66 | "barn"
67 | ],
68 | "entered_zones": [
69 | "lawn"
70 | ],
71 | "has_clip": true,
72 | "has_snapshot": true
73 | },
74 | "type": "new"
75 | }
76 |
--------------------------------------------------------------------------------
/examples/frigate/assets/frigate-event-new-good.json:
--------------------------------------------------------------------------------
1 | {
2 | "before": {
3 | "id": "1680791459.255384-abcdef",
4 | "camera": "cam-testdrive",
5 | "frame_time": 1680791459.255384,
6 | "snapshot_time": 0,
7 | "label": "goat",
8 | "sub_label": null,
9 | "top_score": 0,
10 | "false_positive": true,
11 | "start_time": 1680791459.255384,
12 | "end_time": null,
13 | "current_zones": [],
14 | "entered_zones": [],
15 | "has_clip": false,
16 | "has_snapshot": false
17 | },
18 | "after": {
19 | "id": "1680791459.255384-abcdef",
20 | "camera": "cam-testdrive",
21 | "frame_time": 1680791506.638857,
22 | "snapshot_time": 1680791506.638857,
23 | "label": "goat",
24 | "sub_label": null,
25 | "false_positive": false,
26 | "start_time": 1680791459.255384,
27 | "end_time": null,
28 | "current_zones": [
29 | "barn"
30 | ],
31 | "entered_zones": [
32 | "lawn"
33 | ],
34 | "has_clip": true,
35 | "has_snapshot": true
36 | },
37 | "type": "new"
38 | }
39 |
--------------------------------------------------------------------------------
/examples/frigate/assets/frigate-event-new-ignored.json:
--------------------------------------------------------------------------------
1 | {
2 | "before": {
3 | "id": "1680791459.255384-abcdef",
4 | "camera": "frontyard",
5 | "frame_time": 1680791459.255384,
6 | "snapshot_time": 0,
7 | "label": "goat",
8 | "sub_label": null,
9 | "top_score": 0,
10 | "false_positive": true,
11 | "start_time": 1680791459.255384,
12 | "end_time": null,
13 | "current_zones": [],
14 | "entered_zones": [],
15 | "has_clip": false,
16 | "has_snapshot": false
17 | },
18 | "after": {
19 | "id": "1680791459.255384-abcdef",
20 | "camera": "frontyard",
21 | "frame_time": 1680791506.638857,
22 | "snapshot_time": 1680791506.638857,
23 | "label": "goat",
24 | "sub_label": null,
25 | "false_positive": false,
26 | "start_time": 1680791459.255384,
27 | "end_time": null,
28 | "current_zones": [],
29 | "entered_zones": [
30 | "lawn"
31 | ],
32 | "has_clip": true,
33 | "has_snapshot": true
34 | },
35 | "type": "new"
36 | }
37 |
--------------------------------------------------------------------------------
/examples/frigate/assets/frigate-event-update-good.json:
--------------------------------------------------------------------------------
1 | {
2 | "before": {
3 | "id": "1680791459.255384-abcdef",
4 | "camera": "cam-testdrive",
5 | "frame_time": 1680791459.255384,
6 | "snapshot_time": 0,
7 | "label": "goat",
8 | "sub_label": null,
9 | "top_score": 0,
10 | "false_positive": true,
11 | "start_time": 1680791459.255384,
12 | "end_time": null,
13 | "current_zones": [],
14 | "entered_zones": [],
15 | "has_clip": false,
16 | "has_snapshot": false
17 | },
18 | "after": {
19 | "id": "1680791459.255384-abcdef",
20 | "camera": "cam-testdrive",
21 | "frame_time": 1680791506.638857,
22 | "snapshot_time": 1680791506.638857,
23 | "label": "goat",
24 | "sub_label": null,
25 | "false_positive": false,
26 | "start_time": 1680791459.255384,
27 | "end_time": null,
28 | "current_zones": [
29 | "barn"
30 | ],
31 | "entered_zones": [
32 | "lawn"
33 | ],
34 | "has_clip": true,
35 | "has_snapshot": true
36 | },
37 | "type": "update"
38 | }
39 |
--------------------------------------------------------------------------------
/examples/frigate/assets/frigate-event-update-samezone.json:
--------------------------------------------------------------------------------
1 | {
2 | "before": {
3 | "id": "1680791459.255384-abcdef",
4 | "camera": "cam-testdrive",
5 | "frame_time": 1680791459.255384,
6 | "snapshot_time": 0,
7 | "label": "goat",
8 | "sub_label": null,
9 | "top_score": 0,
10 | "false_positive": true,
11 | "start_time": 1680791459.255384,
12 | "end_time": null,
13 | "current_zones": [],
14 | "entered_zones": [],
15 | "has_clip": false,
16 | "has_snapshot": false
17 | },
18 | "after": {
19 | "id": "1680791459.255384-abcdef",
20 | "camera": "cam-testdrive",
21 | "frame_time": 1680791506.638857,
22 | "snapshot_time": 1680791506.638857,
23 | "label": "goat",
24 | "sub_label": null,
25 | "false_positive": false,
26 | "start_time": 1680791459.255384,
27 | "end_time": null,
28 | "current_zones": [],
29 | "entered_zones": [],
30 | "has_clip": true,
31 | "has_snapshot": true
32 | },
33 | "type": "update"
34 | }
35 |
--------------------------------------------------------------------------------
/examples/frigate/assets/frigate-event-update-stationary.json:
--------------------------------------------------------------------------------
1 | {
2 | "before": {
3 | "id": "1680791459.255384-abcdef",
4 | "camera": "cam-testdrive",
5 | "frame_time": 1680791459.255384,
6 | "snapshot_time": 0,
7 | "label": "goat",
8 | "sub_label": null,
9 | "top_score": 0,
10 | "false_positive": true,
11 | "start_time": 1680791459.255384,
12 | "end_time": null,
13 | "current_zones": [],
14 | "entered_zones": [],
15 | "has_clip": false,
16 | "has_snapshot": false,
17 | "stationary": true
18 | },
19 | "after": {
20 | "id": "1680791459.255384-abcdef",
21 | "camera": "cam-testdrive",
22 | "frame_time": 1680791506.638857,
23 | "snapshot_time": 1680791506.638857,
24 | "label": "goat",
25 | "sub_label": null,
26 | "false_positive": false,
27 | "start_time": 1680791459.255384,
28 | "end_time": null,
29 | "current_zones": [],
30 | "entered_zones": [
31 | "zone1"
32 | ],
33 | "has_clip": true,
34 | "has_snapshot": true,
35 | "stationary": true
36 | },
37 | "type": "update"
38 | }
39 |
--------------------------------------------------------------------------------
/examples/frigate/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3.8"
2 |
3 | services:
4 |
5 | # ---------
6 | # Mosquitto
7 | # ---------
8 | # https://hub.docker.com/_/eclipse-mosquitto
9 | mosquitto:
10 | image: eclipse-mosquitto:${MOSQUITTO_VERSION}
11 | container_name: mosquitto
12 | command: ["mosquitto", "-c", "/mosquitto-no-auth.conf"]
13 | ports:
14 | - "${PORT_MOSQUITTO}:${PORT_MOSQUITTO}"
15 |
16 | # Define health check for Mosquitto.
17 | healthcheck:
18 | test: [ "CMD", "mosquitto_sub", "-v", "-t", "foobar", "-E" ]
19 | start_period: 1s
20 | interval: 3s
21 | timeout: 10s
22 | retries: 60
23 |
24 | # ----
25 | # ntfy
26 | # ----
27 | # https://docs.ntfy.sh/install/#docker
28 | # https://hub.docker.com/r/binwiederhier/ntfy
29 | ntfy:
30 | image: binwiederhier/ntfy:${NTFY_VERSION}
31 | container_name: ntfy
32 | command: >
33 | serve
34 | --base-url="http://localhost:5555"
35 | --attachment-cache-dir="/tmp/ntfy-attachments"
36 | --attachment-expiry-duration="168h"
37 | environment:
38 | # optional: set desired timezone
39 | - TZ=UTC
40 | ports:
41 | - "${PORT_NTFY}:80"
42 | healthcheck:
43 | test: ["CMD-SHELL", "wget -q --tries=1 http://localhost:5555/v1/health -O - | grep -Eo '\"healthy\"\\s*:\\s*true' || exit 1"]
44 | interval: 60s
45 | timeout: 10s
46 | retries: 3
47 | start_period: 40s
48 |
49 | # -------
50 | # Bundler
51 | # -------
52 | # Wait for all defined services to be fully available by probing their health
53 | # status, even when using `docker compose up --detach`.
54 | # https://marcopeg.com/2019/docker-compose-healthcheck/
55 | start-dependencies:
56 | image: dadarek/wait-for-dependencies
57 | depends_on:
58 | mosquitto:
59 | condition: service_healthy
60 | ntfy:
61 | condition: service_healthy
62 |
--------------------------------------------------------------------------------
/examples/frigate/frigate.ini:
--------------------------------------------------------------------------------
1 | # Frigate » Forward events and snapshots to Ntfy, using mqttwarn.
2 | # https://mqttwarn.readthedocs.io/en/latest/examples/frigate/README.html
3 |
4 | [defaults]
5 | functions = frigate.py
6 | launch = ntfy, store-image
7 |
8 | status_publish = True
9 |
10 | # This scenario needs two workers, because it needs the headroom of two threads
11 | # running in parallel, to synchronize _two_ distinct Frigate events with each other,
12 | # in order to send out _one_ notification.
13 | num_workers = 2
14 |
15 |
16 |
17 | # =====================
18 | # Frigate event to ntfy
19 | # =====================
20 |
21 | # Format: JSON
22 | # Docs: https://docs.frigate.video/integrations/mqtt/#frigateevents
23 |
24 | [config:ntfy]
25 | targets = {
26 | 'test': {
27 | 'url': 'http://username:password@localhost:5555/frigate-testdrive',
28 | 'file': '/tmp/mqttwarn-frigate-{camera}-{label}.png',
29 | 'click': 'https://httpbin.org/anything?camera={event.camera}&label={event.label}&zone={event.entered_zones[0]}',
30 | # Wait for the file to arrive for three quarters of a second, and delete it after reading.
31 | '__settings__': {
32 | 'file_retry_tries': 10,
33 | 'file_retry_interval': 0.075,
34 | 'file_unlink': True,
35 | }
36 | }
37 | }
38 |
39 | [frigate/events]
40 | filter = frigate_events_filter()
41 | alldata = frigate_events()
42 | targets = ntfy:test
43 | title = {event.label} entered {event.entered_zones_str} at {event.time}
44 | format = {event.label} was in {event.current_zones_str} before
45 |
46 | # Limit the alert based on camera/zone.
47 | frigate_skip_rules = {
48 | 'rule-1': {'camera': ['frontyard'], 'entered_zones': ['lawn']},
49 | }
50 |
51 |
52 | # =====================
53 | # Frigate image to file
54 | # =====================
55 |
56 | # Format: Binary (PNG or JPEG)
57 | # Docs: https://docs.frigate.video/integrations/mqtt/#frigatecamera_nameobject_namesnapshot
58 |
59 | [config:store-image]
60 | module = file
61 | targets = {
62 | 'cam-testdrive-goat': ['/tmp/mqttwarn-frigate-cam-testdrive-goat.png'],
63 | 'cam-testdrive-squirrel': ['/tmp/mqttwarn-frigate-cam-testdrive-squirrel.png'],
64 | }
65 |
66 | # Configure `file` plugin to pass through payload 1:1.
67 | append_newline = False
68 | decode_utf8 = False
69 | overwrite = True
70 |
71 | [frigate/+/+/snapshot]
72 | alldata = frigate_snapshot_decode_topic()
73 | targets = store-image:{camera_name}-{object_name}
74 |
--------------------------------------------------------------------------------
/examples/frigate/publish.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Check prerequisites.
4 | prerequisites="cat convert jq mosquitto_pub wget"
5 | for program in ${prerequisites}; do
6 | if [ ! "$( command -v "${program}" )" ]; then
7 | echo "ERROR: Program not installed: ${program}"
8 | exit 1
9 | fi
10 | done
11 |
12 | # Acquire image for publishing.
13 | if [ ! -f goat.png ]; then
14 | wget -O goat.png https://user-images.githubusercontent.com/453543/231550862-5a64ac7c-bdfa-4509-86b8-b1a770899647.png
15 | fi
16 |
17 | # 1. Publish picture snapshot in PNG format.
18 | mosquitto_pub -f goat.png -t 'frigate/cam-testdrive/goat/snapshot'
19 |
20 | # 2. Publish event in JSON format.
21 | # shellcheck disable=SC2002
22 | cat "assets/frigate-event-new-good.json" | jq -c | mosquitto_pub -t 'frigate/events' -l
23 |
--------------------------------------------------------------------------------
/examples/hiveeyes/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mqtt-tools/mqttwarn/d9d312df66b428bf36c053ebaa88e6de18f8aa37/examples/hiveeyes/__init__.py
--------------------------------------------------------------------------------
/examples/homie/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mqtt-tools/mqttwarn/d9d312df66b428bf36c053ebaa88e6de18f8aa37/examples/homie/__init__.py
--------------------------------------------------------------------------------
/examples/homie/homie.ini:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Demonstrate Homie function extensions for mqttwarn
3 |
4 | ; ========
5 | ; Synopsis
6 | ; ========
7 | ;
8 | ; Run mqttwarn::
9 | ;
10 | ; export MQTTWARNINI=examples/homie/homie.ini
11 | ; mqttwarn
12 | ;
13 | ; Send some homie-like data::
14 | ;
15 | ; mosquitto_pub -t homie/bee1/weight/value -m 42.42
16 |
17 |
18 | ; ==================
19 | ; Base configuration
20 | ; ==================
21 |
22 | [defaults]
23 | hostname = 'localhost'
24 | clientid = 'mqttwarn'
25 |
26 | ; logging
27 | logformat = '%(asctime)-15s %(levelname)-5s [%(module)s] %(message)s'
28 | logfile = stream://sys.stderr
29 |
30 | ; one of: CRITICAL, DEBUG, ERROR, INFO, WARN
31 | #loglevel = INFO
32 | loglevel = DEBUG
33 |
34 | ; enable service providers
35 | launch = log, file
36 |
37 | ; number of notification dispatcher threads
38 | num_workers = 3
39 |
40 | ; path to file containing self-defined functions
41 | functions = 'examples.homie.homie'
42 |
43 |
44 | ; ================
45 | ; check_mk routing
46 | ; ================
47 |
48 | ; See also https://github.com/mqtt-tools/mqttwarn/wiki/Incorporating-topic-names#incorporate-topic-names-into-topic-targets
49 |
50 | [check_mk_universal]
51 | topic = homie/+/+/value
52 | datamap = decode_homie_topic()
53 | targets = file:cmk_spool
54 | format = <<<<{device}>>>>\n<<>>\n 0 {node} {node}={payload} {node}: {payload}
55 |
56 | [config:file]
57 | append_newline = True
58 | overwrite = True
59 | targets = {
60 | 'cmk_spool': ['/var/lib/check_mk_agent/spool/300{device}-{node}'],
61 | }
62 |
63 |
64 | ; ===============
65 | ; Regular logging
66 | ; ===============
67 |
68 | [homie-logging]
69 | ; Just log all incoming messages
70 | topic = homie/#
71 | targets = log:info
72 |
73 | [config:log]
74 | targets = {
75 | 'debug' : [ 'debug' ],
76 | 'info' : [ 'info' ],
77 | 'warn' : [ 'warn' ],
78 | 'crit' : [ 'crit' ],
79 | 'error' : [ 'error' ]
80 | }
81 |
82 |
--------------------------------------------------------------------------------
/examples/homie/homie.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Homie function extensions for mqttwarn
3 | import re
4 |
5 |
6 | # ------------------------------------------
7 | # Synopsis
8 | # ------------------------------------------
9 | #
10 | # Run mqttwarn::
11 | #
12 | # export MQTTWARNINI=examples/homie/homie.ini
13 | # mqttwarn
14 | #
15 | # Send some homie-like data::
16 | #
17 | # mosquitto_pub -t homie/bee1/weight/value -m 42.42
18 |
19 |
20 | def decode_homie_topic(topic):
21 | """
22 | Split Homie-style MQTT topic path into segments for
23 | enriching transformation data inside mqttwarn.
24 | """
25 | if type(topic) == str:
26 | try:
27 | pattern = r'^(?P.+?)/(?P.+?)/(?P.+?)/(?P.+?)$'
28 | p = re.compile(pattern)
29 | m = p.match(topic)
30 | topology = m.groupdict()
31 | except:
32 | topology = {}
33 | return topology
34 | return None
35 |
36 |
--------------------------------------------------------------------------------
/examples/mediaplayer/mqttwarn-mplayer.ini:
--------------------------------------------------------------------------------
1 | # Simple MQTT media player using mqttwarn and mplayer.
2 | # https://mqttwarn.readthedocs.io/en/latest/examples/mediaplayer/readme.html
3 |
4 | [defaults]
5 | launch = execute
6 |
7 | [config:execute]
8 | targets = {
9 | 'mediaplayer-play': [ 'mplayer', '-volume', '80', '[TEXT]' ],
10 | }
11 |
12 | [mediaplayer/play]
13 | targets = execute:mediaplayer-play
14 |
--------------------------------------------------------------------------------
/examples/mediaplayer/readme.md:
--------------------------------------------------------------------------------
1 | {#mqtt-media-player}
2 | # Simple MQTT media player
3 |
4 |
5 | ## About
6 |
7 | The idea is to implement a simple MQTT media player on Linux, which can be used to play TTS
8 | messages from Home Assistant. Home Assistant renders the TTS stream as an MP3 and makes it
9 | available on its HTTP server, so a corresponding command using `mplayer` to play the audio
10 | resource would look like this.
11 | ```shell
12 | mplayer -volume 80 http://home.assistant.address:8123/api/tts_proxy/6a0efdf280bf8c79a.mp3
13 | ```
14 |
15 | ## Configuration
16 |
17 | The solution for this will be implemented using mqttwarn's [](#execute) service plugin, which
18 | can be used to invoke programs, and interpolate MQTT payload data.
19 |
20 | :::{literalinclude} mqttwarn-mplayer.ini
21 | :language: ini
22 | :::
23 |
24 |
25 | ## Usage
26 | Using three terminal sessions, you can exercise the example interactively. First, let's start
27 | the [Mosquitto] MQTT broker.
28 | ```shell
29 | docker run --name=mosquitto -it --rm --publish=1883:1883 eclipse-mosquitto:2.0 mosquitto -c /mosquitto-no-auth.conf
30 | ```
31 | Let's acquire the `mqttwarn-mplayer.ini` configuration file, and start `mqttwarn`.
32 | ```shell
33 | wget https://github.com/mqtt-tools/mqttwarn/raw/main/examples/mediaplayer/mqttwarn-mplayer.ini
34 | mqttwarn --config-file=mqttwarn-mplayer.ini
35 | ```
36 | Now, when publishing the URL to the audio resource on the designated MQTT topic, `mqttwarn` will
37 | invoke the `mplayer` command as instructed.
38 | ```shell
39 | echo 'http://home.assistant.address:8123/api/tts_proxy/6a0efdf280bf8c79a.mp3' | \
40 | mosquitto_pub -t 'mediaplayer/play' -l
41 | ```
42 |
43 |
44 | [Home Assistant]: https://www.home-assistant.io/
45 | [Mosquitto]: https://mosquitto.org
46 |
--------------------------------------------------------------------------------
/examples/owntracks-ntfy/mqttwarn-owntracks.ini:
--------------------------------------------------------------------------------
1 | # Forward OwnTracks low-battery warnings to ntfy.
2 | # https://mqttwarn.readthedocs.io/en/latest/examples/owntracks-ntfy/readme.html
3 |
4 | [defaults]
5 | functions = mqttwarn-owntracks.py
6 | launch = ntfy
7 |
8 | [config:ntfy]
9 | targets = {'testdrive': 'https://ntfy.sh/testdrive'}
10 |
11 | [owntracks/#]
12 | filter = owntracks_batteryfilter()
13 | format = My phone battery is getting low ({batt}%)!
14 | targets = ntfy:testdrive
15 |
--------------------------------------------------------------------------------
/examples/owntracks-ntfy/mqttwarn-owntracks.py:
--------------------------------------------------------------------------------
1 | """
2 | Forward OwnTracks low-battery warnings to ntfy.
3 | https://mqttwarn.readthedocs.io/en/latest/examples/owntracks-ntfy/readme.html
4 | """
5 | import json
6 |
7 |
8 | def owntracks_batteryfilter(topic: str, message: str):
9 | ignore = True
10 | try:
11 | data = dict(json.loads(message).items())
12 | except:
13 | data = None
14 |
15 | if data and "batt" in data and data["batt"] is not None:
16 | ignore = int(data["batt"]) > 20
17 |
18 | return ignore
19 |
--------------------------------------------------------------------------------
/examples/readme.md:
--------------------------------------------------------------------------------
1 | # Examples
2 |
3 | This section contains a few examples demonstrating what you can do with mqttwarn,
4 | and about how mqttwarn can be used with more advanced configurations.
5 |
6 | :::{toctree}
7 | :maxdepth: 1
8 | alexa/readme.md
9 | arduino-temperature/readme.md
10 | frigate/README.rst
11 | mediaplayer/readme.md
12 | owntracks-ntfy/readme.md
13 | warntoggle/README.rst
14 | zabbix-iot/readme.md
15 | :::
16 |
--------------------------------------------------------------------------------
/examples/warntoggle/README.rst:
--------------------------------------------------------------------------------
1 | ################################
2 | Button toggle with web interface
3 | ################################
4 |
5 | About
6 | =====
7 |
8 | A custom function to be used when configuring streams in ``mqttwarn``.
9 | ``warntoggle`` makes it easy to toggle notifications "on" or "off"
10 | through a simple web interface.
11 |
12 | Implementation
13 | ==============
14 |
15 | - ``mqttwarn.ini`` has a section ``[+/temperature]``, which applies to all MQTT
16 | messages received on matching topics. This section includes a filter,
17 | referring to a user-defined function.
18 | - When an MQTT message is received, the user-defined function is triggered.
19 | - The user-defined function checks a JSON file for the topic, and based on
20 | ``TRUE`` or ``FALSE`` setting, will return a message back to ``mqttwarn``, to
21 | either allow the notification to happen or not.
22 | - If an MQTT topic was not found within the aforementioned JSON file, it will be
23 | added and set with a configurable default value.
24 | - For the user, an accompanying web script allows the toggle value to be changed.
25 |
26 | Installation
27 | ============
28 |
29 | - Copy the ``togglestate()`` function from ``mqttwarn/customfunctions.py`` to
30 | your own user-defined functions file, or copy the entire file and refer to it
31 | in ``mqttwarn.ini`` with a ``functions = 'customfunctions.py'`` directive.
32 | - Copy the content of the ``www`` folder to a web server on the same host as
33 | ``mqttwarn``, ensure Python is enabled for the server, and ``warntoggle.json``
34 | is writeable by the web server.
35 | - Create a symbolic link from ``/etc/mqttwarn/warntoggle.json`` to ``/var/www/html/warntoggle.json`,
36 | adjusted for your local situation. Alternatively, configure the filename inside
37 | the custom function where it now says ``filename = "warntoggle.json"``, to
38 | contain an absolute path.
39 | - For each stream you wish to be considered by ``warntoggle``, add a line
40 | ``filter = togglestate()`` to ``mqttwarn.ini``. This must be done inside each
41 | stream section.
42 |
--------------------------------------------------------------------------------
/examples/warntoggle/mqttwarn/customfunctions.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 |
4 | def togglestate(topic, payload, section, srv):
5 | filename = "warntoggle.json"
6 | default_topicblock = False
7 |
8 | try:
9 | with open(filename) as infile:
10 | toggles = json.load(infile)
11 | infile.close()
12 |
13 | if topic in toggles:
14 | # file found, topic found
15 | topicblock = toggles[topic]
16 | srv.logging.debug('togglestate() was called from the {} section and found {} in {}'.format(
17 | section, topic, filename))
18 | else:
19 | # file found, adding new topic
20 | toggles[topic] = default_topicblock
21 | with open(filename, 'w') as outfile:
22 | json.dump(toggles, outfile)
23 | outfile.close()
24 |
25 | topicblock = default_topicblock
26 | srv.logging.debug('togglestate() was called from the {} section, did not find {} in {}'.format(
27 | section, topic, filename))
28 | srv.logging.debug('togglestate() added {} to {} with blocking set to {}'.format(
29 | topic, filename, topicblock))
30 |
31 | except Exception as e:
32 | # file not found or other error
33 | topicblock = default_topicblock
34 | srv.logging.debug('togglestate() encountered an error: {}'.format(e))
35 |
36 | srv.logging.debug('togglestate() will return {}'.format(topicblock))
37 | return topicblock
38 |
--------------------------------------------------------------------------------
/examples/warntoggle/www/warntoggle.json:
--------------------------------------------------------------------------------
1 | {
2 | "livingroom/temperature": true,
3 | "bedroom/temperature": true,
4 | "bedroom/lights": false
5 | }
6 |
--------------------------------------------------------------------------------
/examples/warntoggle/www/warntoggle.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | import json
4 |
5 | def index(req):
6 |
7 | filename = "/var/www/html/warntoggle/warntoggle.json"
8 | writetoggles = {}
9 | readtoggles = {}
10 | scriptname = __file__.split('/')[-1:][0]
11 |
12 | # write form data to the file if applicable
13 |
14 | if bool(req.form):
15 |
16 | for k,v in req.form.items():
17 | writetoggles[k] = eval(str(v))
18 |
19 | with open(filename, 'w') as outfile:
20 | json.dump(writetoggles, outfile)
21 | outfile.close()
22 |
23 | # read data from the file
24 |
25 | with open(filename, 'r') as infile:
26 | readtoggles = json.load(infile)
27 | infile.close()
28 |
29 | # display the form
30 |
31 | html = "mqttwarn notification toggles
"
32 | html += "
"
46 | html += " reload"
47 | html += ""
48 | html += ""
49 |
50 | return html
51 |
--------------------------------------------------------------------------------
/examples/zabbix-iot/mqttwarn-zabbix-iot.ini:
--------------------------------------------------------------------------------
1 | [defaults]
2 | functions = 'mqttwarn-zabbix-iot.py'
3 | launch = zabbix
4 |
5 | [config:zabbix]
6 | targets = {
7 | 't1' : [ 'localhost', 10051 ],
8 | }
9 |
10 | [tele/#]
11 | alldata = decode_for_zabbix()
12 | targets = zabbix:t1
13 |
--------------------------------------------------------------------------------
/examples/zabbix-iot/mqttwarn-zabbix-iot.py:
--------------------------------------------------------------------------------
1 | def decode_for_zabbix(topic, data, srv=None):
2 | status_key = None
3 |
4 | # the first part (part[0]) is always tele
5 | # the second part (part[1]) is the device, the value comes from
6 | # the third part (part[2]) is the name of the metric (e.g. temperature/humidity/voltage...)
7 | parts = topic.split('/')
8 | client = parts[1]
9 | key = parts[2]
10 |
11 | return dict(client=client, key=key, status_key=status_key)
12 |
--------------------------------------------------------------------------------
/mqttwarn/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # (c) 2014-2023 The mqttwarn developers
3 |
4 | __author__ = "Jan-Piet Mens , Ben Jones "
5 | __copyright__ = "Copyright 2014-2022 Jan-Piet Mens"
6 | __license__ = "Eclipse Public License - v 2.0 (http://www.eclipse.org/legal/epl-2.0/)"
7 |
8 | try:
9 | from importlib.metadata import version
10 | except ImportError: # pragma: nocover
11 | from importlib_metadata import version # type: ignore[no-redef]
12 |
13 | __version__ = version("mqttwarn")
14 |
--------------------------------------------------------------------------------
/mqttwarn/__main__.py:
--------------------------------------------------------------------------------
1 | import mqttwarn.commands
2 |
3 | mqttwarn.commands.run()
4 |
--------------------------------------------------------------------------------
/mqttwarn/examples/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mqtt-tools/mqttwarn/d9d312df66b428bf36c053ebaa88e6de18f8aa37/mqttwarn/examples/__init__.py
--------------------------------------------------------------------------------
/mqttwarn/services/README.md:
--------------------------------------------------------------------------------
1 | This directory contains one plugin file (`.py`) per service.
2 |
--------------------------------------------------------------------------------
/mqttwarn/services/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mqtt-tools/mqttwarn/d9d312df66b428bf36c053ebaa88e6de18f8aa37/mqttwarn/services/__init__.py
--------------------------------------------------------------------------------
/mqttwarn/services/alexa-notify-me.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | """
4 | __author__ = 'Bram Hendrickx'
5 | __copyright__ = 'Copyright 2016 Bram Hendrickx'
6 | __license__ = 'Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)'
7 |
8 | Original script by Bram Hendrickx. https://github.com/mqtt-tools/mqttwarn/blob/main/mqttwarn/services/ifttt.py
9 | Modified to work with notify-me app for Alexa. http://www.thomptronics.com/notify-me
10 | """
11 |
12 | __author__ = 'Michael Brougham'
13 | __copyright__ = 'Copyright 2018 Michael Brougham'
14 | __license__ = 'Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)'
15 |
16 | import json
17 | import requests
18 |
19 |
20 | def plugin(srv, item):
21 | """ expects (key) in addrs """
22 |
23 | srv.logging.debug("*** MODULE=%s: service=%s, target=%s", __file__, item.service, item.target)
24 |
25 | try:
26 | srv.logging.debug("Sending to NotifyMe service")
27 | body = json.dumps({
28 | "notification": item.message,
29 | "accessCode": item.addrs[0]
30 | })
31 |
32 | response = requests.post(url="https://api.notifymyecho.com/v1/NotifyMe", data=body)
33 | response.raise_for_status()
34 |
35 | srv.logging.debug("Successfully sent to NotifyMe service")
36 |
37 | except Exception as e:
38 | srv.logging.warning("Failed to send message to NotifyMe service: %s" % e)
39 | return False
40 |
41 | return True
42 |
--------------------------------------------------------------------------------
/mqttwarn/services/amqp.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | __author__ = 'Jan-Piet Mens '
5 | __copyright__ = 'Copyright 2014 Jan-Piet Mens'
6 | __license__ = 'Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)'
7 |
8 | import pytest
9 |
10 | puka = pytest.importorskip("puka")
11 |
12 |
13 | def plugin(srv, item):
14 | srv.logging.debug("*** MODULE=%s: service=%s, target=%s", __file__, item.service, item.target)
15 |
16 | uri = item.config['uri']
17 |
18 | exchange, routing_key = item.addrs
19 |
20 | try:
21 | srv.logging.debug("AMQP publish to %s [%s/%s]" % (item.target, exchange, routing_key))
22 |
23 | client = puka.Client(uri)
24 | promise = client.connect()
25 | client.wait(promise)
26 |
27 | headers = {
28 | 'content_type': 'text/plain',
29 | 'x-agent': 'mqttwarn',
30 | 'delivery_mode': 1,
31 | }
32 | promise = client.basic_publish(exchange=exchange,
33 | routing_key=routing_key,
34 | headers=headers,
35 | body=item.message)
36 | client.wait(promise)
37 | client.close()
38 |
39 | srv.logging.debug("Successfully published AMQP notification")
40 | except Exception as e:
41 | srv.logging.warning("Error on AMQP publish to %s [%s/%s]: %s" % (item.target, exchange, routing_key, e))
42 | return False
43 |
44 | return True
45 |
--------------------------------------------------------------------------------
/mqttwarn/services/apns.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | __author__ = 'Jan-Piet Mens '
5 | __copyright__ = 'Copyright 2014 Jan-Piet Mens'
6 | __license__ = 'Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)'
7 |
8 | import json
9 | try:
10 | from apns import APNs, Payload
11 | except:
12 | pass
13 |
14 |
15 | def plugin(srv, item):
16 | addrs = item.addrs
17 | data = item.data
18 | text = item.message
19 |
20 | srv.logging.debug("*** MODULE=%s: service=%s, target=%s", __file__, item.service, item.target)
21 |
22 | try:
23 | cert_file, key_file = addrs
24 | except:
25 | srv.logging.warning("Incorrect service configuration")
26 | return False
27 |
28 | if 'apns_token' not in data:
29 | srv.logging.warning("Cannot notify via APNS: apns_token is missing")
30 | return False
31 |
32 | apns_token = data['apns_token']
33 |
34 | custom = {}
35 | try:
36 | payload = data['payload']
37 | mdata = json.loads(payload)
38 | if 'custom' in mdata:
39 | custom = mdata['custom']
40 | except:
41 | pass
42 |
43 | apns = APNs(use_sandbox=False, cert_file=cert_file, key_file=key_file)
44 |
45 | pload = Payload(alert=text, custom=custom, sound="default", badge=1)
46 | apns.gateway_server.send_notification(apns_token, pload)
47 |
48 | srv.logging.debug("Successfully published APNS notification to %s" % apns_token)
49 |
50 | return True
51 |
--------------------------------------------------------------------------------
/mqttwarn/services/apprise.py:
--------------------------------------------------------------------------------
1 | import warnings
2 |
3 | from mqttwarn.services.apprise_single import plugin
4 |
5 | warnings.warn("`mqttwarn.services.apprise` will be removed in a future release of mqttwarn. "
6 | "Please use `mqttwarn.services.apprise_single` or `mqttwarn.services.apprise_multi` instead.",
7 | category=DeprecationWarning)
8 |
--------------------------------------------------------------------------------
/mqttwarn/services/apprise_single.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | __author__ = 'Andreas Motl '
4 | __copyright__ = 'Copyright 2020-2021 Andreas Motl'
5 | __license__ = 'Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)'
6 |
7 | # https://github.com/caronc/apprise#developers
8 | from collections import OrderedDict
9 |
10 | import apprise
11 |
12 | from mqttwarn.services.apprise_util import obtain_apprise_arguments, add_url_params, get_all_template_argument_names
13 |
14 | APPRISE_ALL_ARGUMENT_NAMES = get_all_template_argument_names()
15 |
16 |
17 | def plugin(srv, item):
18 | """Send a message to a single Apprise plugin."""
19 |
20 | srv.logging.debug("*** MODULE=%s: service=%s, target=%s", __file__, item.service, item.target)
21 |
22 | sender = item.config.get('sender')
23 | sender_name = item.config.get('sender_name')
24 | baseuri = item.config['baseuri']
25 | addresses = item.addrs
26 | title = item.title
27 | body = item.message
28 |
29 | try:
30 | srv.logging.debug("Sending notification to Apprise. target=%s, addresses=%s" % (item.target, addresses))
31 | to = ','.join(addresses)
32 |
33 | # Disable the Apprise rate limiting subsystem.
34 | try:
35 | from apprise.plugins.NotifyBase import NotifyBase
36 | NotifyBase.request_rate_per_sec = 0
37 | except ImportError:
38 | pass
39 |
40 | # Create an Apprise instance.
41 | apobj = apprise.Apprise(asset=apprise.AppriseAsset(async_mode=False))
42 |
43 | # Collect URL parameters.
44 | params = OrderedDict()
45 |
46 | # Obtain and apply all possible Apprise parameters from data dictionary.
47 | params.update(obtain_apprise_arguments(item, APPRISE_ALL_ARGUMENT_NAMES))
48 |
49 | # Apply addressee information.
50 | if sender:
51 | params["from"] = sender
52 | if to:
53 | params["to"] = to
54 | if sender_name:
55 | params["name"] = sender_name
56 |
57 | # Add parameters to Apprise notification URL.
58 | uri = add_url_params(baseuri, params)
59 | apobj.add(uri)
60 |
61 | # Submit notification.
62 | outcome = apobj.notify(
63 | body=body,
64 | title=title,
65 | )
66 |
67 | if outcome:
68 | srv.logging.info("Successfully sent message using Apprise")
69 | return True
70 |
71 | else:
72 | srv.logging.error("Sending message using Apprise failed")
73 | return False
74 |
75 | except Exception as e:
76 | srv.logging.error("Sending message using Apprise failed. target=%s, error=%s" % (item.target, e))
77 | return False
78 |
--------------------------------------------------------------------------------
/mqttwarn/services/apprise_util.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # (c) 2021-2023 The mqttwarn developers
3 | from __future__ import absolute_import
4 |
5 | from functools import lru_cache
6 | from urllib.parse import urlparse, urlencode
7 |
8 | from apprise import Apprise, ContentLocation
9 |
10 | from mqttwarn.model import ProcessorItem
11 |
12 |
13 | @lru_cache(maxsize=None)
14 | def get_all_template_argument_names():
15 | """
16 | Inquire all possible parameter names from all Apprise plugins.
17 | """
18 | a = Apprise(asset=None, location=ContentLocation.LOCAL)
19 | results = a.details()
20 | plugin_infos = results['schemas']
21 |
22 | all_arg_names = []
23 | for plugin_info in plugin_infos:
24 | arg_names = plugin_info["details"]["args"].keys()
25 | all_arg_names += arg_names
26 |
27 | return sorted(set(all_arg_names))
28 |
29 |
30 | def obtain_apprise_arguments(item: ProcessorItem, arg_names: list) -> dict:
31 | """
32 | Obtain eventual Apprise parameters from data dictionary.
33 | """
34 | params = dict()
35 | for arg_name in arg_names:
36 | if isinstance(item.data, dict) and arg_name in item.data:
37 | params[arg_name] = item.data[arg_name]
38 | return params
39 |
40 |
41 | def add_url_params(url: str, params: dict) -> str:
42 | """
43 | Serialize query parameter dictionary and add it to URL.
44 | """
45 | url_parsed = urlparse(url)
46 | if params:
47 | seperator = "?"
48 | if url_parsed.query:
49 | seperator = "&"
50 | url += seperator + urlencode(params)
51 | return url
52 |
--------------------------------------------------------------------------------
/mqttwarn/services/asterisk.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | __author__ = 'Artem Alexandrov '
5 | __copyright__ = 'Copyright 2014 Artem Alexandrov'
6 | __license__ = """Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)"""
7 |
8 | # Please install pyst2
9 | import asterisk.manager
10 |
11 | def plugin(srv, item):
12 |
13 | srv.logging.debug("*** MODULE=%s: service=%s, target=%s", __file__, item.service, item.target)
14 |
15 | host = item.config['host']
16 | port = item.config['port']
17 | username = item.config['username']
18 | password = item.config['password']
19 | extension = item.config['extension']
20 | context = item.config['context']
21 |
22 | gateway = item.addrs[0]
23 | number = item.addrs[1]
24 | title = item.title
25 | message = item.message
26 |
27 | try:
28 | manager = asterisk.manager.Manager()
29 | manager.connect(host, port)
30 | response = manager.login(username, password)
31 | srv.logging.debug("Authentication {}".format(response))
32 | channel = gateway + number
33 | channel_vars = {'text': message}
34 | # originate the call
35 | response = manager.originate(channel, extension, context=context, priority='1', caller_id=extension, variables=channel_vars)
36 | srv.logging.info("Call {}".format(response))
37 | manager.logoff()
38 | except asterisk.manager.ManagerSocketException as e:
39 | srv.logging.error("Error connecting to the manager: {}".format(e))
40 | return False
41 | except asterisk.manager.ManagerAuthException as e:
42 | srv.logging.error("Error logging in to the manager: {}".format(e))
43 | return False
44 | except asterisk.manager.ManagerException as e:
45 | srv.logging.error("Error: {}".format(e))
46 | return False
47 |
48 | # Remember to clean up
49 | finally:
50 | try:
51 | manager.close()
52 | except asterisk.manager.ManagerSocketException: # pragma: no cover
53 | pass
54 |
55 | return True
56 |
--------------------------------------------------------------------------------
/mqttwarn/services/autoremote.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | """
4 | __author__ = 'Bram Hendrickx'
5 | __copyright__ = 'Copyright 2016 Bram Hendrickx'
6 | __license__ = 'Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)'
7 |
8 | Original script by Bram Hendrickx. https://github.com/mqtt-tools/mqttwarn/blob/main/mqttwarn/services/ifttt.py
9 | Modified to work with the autoremote api https://joaoapps.com/autoremote/
10 | """
11 |
12 | __author__ = 'Michael Brougham'
13 | __copyright__ = 'Copyright 2018 Michael Brougham'
14 | __license__ = 'Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)'
15 |
16 | import requests
17 |
18 |
19 | def plugin(srv, item):
20 | ''' expects (apikey, password, target, group, ttl) in addrs '''
21 |
22 | srv.logging.debug("*** MODULE=%s: service=%s, target=%s", __file__, item.service, item.target)
23 |
24 | try:
25 | srv.logging.debug("Sending to autoremote service")
26 | params = {
27 | 'key': item.addrs[0],
28 | 'message': item.message,
29 | 'target': item.addrs[2],
30 | 'sender': item.topic,
31 | 'password': item.addrs[1],
32 | 'ttl': item.addrs[4],
33 | 'collapseKey': item.addrs[3],
34 | }
35 | requests.get('https://autoremotejoaomgcd.appspot.com/sendmessage', params=params)
36 | srv.logging.debug("Successfully sent to autoremote service")
37 |
38 | except Exception as e:
39 | srv.logging.warning("Failed to send message to autoremote service: %s" % e)
40 | return False
41 |
42 | return True
43 |
--------------------------------------------------------------------------------
/mqttwarn/services/azure_iot.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | from six import string_types
4 |
5 | __author__ = 'Morten Høybye Frederiksen '
6 | __copyright__ = 'Copyright 2016-2020 Morten Høybye Frederiksen'
7 | __license__ = 'Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)'
8 |
9 | from builtins import str
10 | import paho.mqtt.publish as mqtt
11 | import paho.mqtt.client as mqttclient
12 | import ssl
13 |
14 | def plugin(srv, item):
15 | srv.logging.debug("*** MODULE=%s: service=%s, target=%s", __file__, item.service, item.target)
16 |
17 | # addrs is a list[] containing device id and sas token
18 | deviceid, sastoken = item.addrs
19 |
20 | # iot hub name and qos is stored in config
21 | iothubname = item.config['iothubname']
22 | qos = int(item.config.get('qos', 0))
23 | if qos < 0 or qos > 1:
24 | srv.logging.error("Only QoS 0 or 1 allowed for Azure IoT Hub, not '%s'" % str(qos))
25 | return False
26 |
27 | # connection info...
28 | params = {
29 | 'hostname': iothubname + ".azure-devices.net",
30 | 'port': 8883,
31 | 'protocol': mqttclient.MQTTv311,
32 | 'qos': qos,
33 | 'retain': False,
34 | 'client_id': deviceid,
35 | }
36 | auth = {
37 | 'username': iothubname + ".azure-devices.net/" +
38 | deviceid + "/?api-version=2018-06-30",
39 | 'password': sastoken
40 | }
41 | tls = {
42 | 'ca_certs': None,
43 | 'certfile': None,
44 | 'keyfile': None,
45 | 'tls_version': ssl.PROTOCOL_TLSv1_2,
46 | 'ciphers': None,
47 | 'cert_reqs': ssl.CERT_NONE
48 | }
49 |
50 | # prepare topic
51 | d2c_topic = "devices/" + deviceid + "/messages/events/"
52 |
53 | # prepare payload
54 | try:
55 | if isinstance(item.message, string_types):
56 | payload = bytearray(item.message, 'utf8')
57 | else:
58 | payload = item.message
59 | except Exception as e:
60 | srv.logging.error("Unable to prepare message for target=%s: %s" % (item.target, str(e)))
61 | return False
62 |
63 | # publish...
64 | try:
65 | srv.logging.debug("Publishing to Azure IoT Hub for target=%s (%s): %s '%s'" % (item.target, deviceid, d2c_topic, str(payload)))
66 | mqtt.single(d2c_topic, payload, auth=auth, tls=tls, **params)
67 | except Exception as e:
68 | srv.logging.error("Unable to publish to Azure IoT Hub for target=%s (%s): %s" % (item.target, deviceid, str(e)))
69 | return False
70 |
71 | return True
72 |
--------------------------------------------------------------------------------
/mqttwarn/services/carbon.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | __author__ = 'Jan-Piet Mens '
5 | __copyright__ = 'Copyright 2014 Jan-Piet Mens'
6 | __license__ = 'Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)'
7 |
8 | import time
9 | import socket
10 |
11 |
12 | def plugin(srv, item):
13 |
14 | srv.logging.debug("*** MODULE=%s: service=%s, target=%s", __file__, item.service, item.target)
15 |
16 | # item.config is brought in from the configuration file
17 | config = item.config
18 |
19 | # addrs is a list[] associated with a particular target.
20 |
21 | try:
22 | carbon_host, carbon_port = item.addrs
23 | carbon_port = int(carbon_port)
24 | except:
25 | srv.logging.error("Configuration for target `carbon' is incorrect")
26 | return False
27 |
28 | # If the incoming payload has been transformed, use that,
29 | # else the original payload
30 | text = item.message
31 |
32 | try:
33 | parts = text.split()
34 | except:
35 | srv.logging.error("target `carbon': cannot split string")
36 | return False
37 |
38 | parts_count = len(parts)
39 | if parts_count == 1:
40 | metric_name = item.data.get('topic', 'ohno').replace('/', '.')
41 | value = parts[0]
42 | tics = int(time.time())
43 | elif parts_count == 2:
44 | metric_name = parts[0]
45 | value = parts[1]
46 | tics = int(time.time())
47 | elif parts_count == 3:
48 | metric_name = parts[0]
49 | value = parts[1]
50 | tics = int(parts[2])
51 | else:
52 | srv.logging.error("target `carbon': error decoding message")
53 | return False
54 |
55 | if metric_name.startswith('.'): # omit dot there caused by useless leading slash in topic
56 | metric_name = metric_name[1:]
57 | carbon_msg = "%s %s %d" % (metric_name, value, tics)
58 | srv.logging.debug("Sending to carbon: %s" % (carbon_msg))
59 | carbon_msg = carbon_msg + "\n"
60 | try:
61 | sock = socket.socket()
62 | sock.connect((carbon_host, carbon_port))
63 | sock.sendall(carbon_msg)
64 | sock.close()
65 | except Exception as e:
66 | srv.logging.warning("Cannot send to carbon service %s:%d: %s" % (carbon_host, carbon_port, e))
67 | return False
68 |
69 | return True
70 |
--------------------------------------------------------------------------------
/mqttwarn/services/celery.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | __author__ = 'Orhan Hirsch '
5 | __copyright__ = 'Copyright 2017 Orhan Hirsch'
6 | __license__ = 'Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)'
7 |
8 | import json
9 | import celery
10 |
11 |
12 | def plugin(srv, item):
13 | srv.logging.debug("*** MODULE=%s: service=%s, target=%s", __file__, item.service, item.target)
14 |
15 | config = item.config
16 |
17 | app = celery.Celery(
18 | config['app_name'],
19 | broker=config['broker_url']
20 | )
21 |
22 | for target in item.addrs:
23 | message = item.message
24 | try:
25 | if target['message_format'] == 'json':
26 | message = json.loads(message)
27 | app.send_task(target['task'], [message])
28 | except Exception as e:
29 | srv.logging.warning("Error: %s" % e)
30 | return False
31 |
32 | return True
33 |
--------------------------------------------------------------------------------
/mqttwarn/services/chromecast.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | """
4 | Text To Speech (TTS) for Chromecast devices, including Google Home Speakers
5 |
6 | pip install pychromecast
7 |
8 | See also https://github.com/skorokithakis/catt.
9 | """
10 |
11 | __author__ = 'Chris Clark '
12 | __copyright__ = 'Copyright 2020 Chris Clark'
13 | __license__ = 'Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)'
14 |
15 |
16 | import os
17 | from urllib.parse import urlencode
18 |
19 |
20 | def plugin(srv, item):
21 | srv.logging.debug("*** MODULE=%s: service=%s, target=%s", __file__, item.service, item.target)
22 |
23 | message = item.message
24 | mime_type = item.config.get('mimetype', 'audio/mp3') # Google translate emits mp3, NOTE Chromecast devices appear to not care if incorrect
25 | base_url = item.config.get('baseuri', os.environ.get('GOOGLE_TRANSLATE_URL', 'https://translate.google.com/translate_tts?'))
26 | lang = item.config.get('lang', 'en') # English
27 |
28 | # Generate a Google Translate (compatible) URL to generate TTS
29 | vars = {
30 | 'q': message,
31 | 'l': lang,
32 | 'tl': lang,
33 | 'client': 'tw-ob',
34 | 'ttsspeed': 1,
35 | 'total': 1,
36 | 'ie': 'UTF-8',
37 | # looks like can get away with out 'textlen'
38 | }
39 | url = base_url + urlencode(vars)
40 |
41 |
42 | # TODO disable pychromecast library logging?
43 | import pychromecast # Some plugin systems want lazy loading, defer import until about to use it
44 |
45 | chromecasts, browser = pychromecast.get_listed_chromecasts(friendly_names=item.addrs)
46 | if not chromecasts:
47 | return False
48 | for cast in chromecasts:
49 | cast.wait()
50 | #srv.logging.debug("cast=%r", cast)
51 | """
52 | print('%r' % (cast.device.friendly_name,))
53 | print('%r' % (cast.socket_client.host,))
54 | """
55 | mc = cast.media_controller
56 | mc.play_media(url, content_type=mime_type)
57 | mc.block_until_active()
58 | mc.play() # issue play, return immediately
59 | # TODO detect when play failed
60 | # if one end point works, but another fails is that a success or a failure?
61 | # What is an end point does NOT show up in list? i.e. len(item.addrs) > len(chromecasts)
62 |
63 | return True
64 |
65 |
--------------------------------------------------------------------------------
/mqttwarn/services/dbus.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | __author__ = 'Fabian Affolter '
5 | __copyright__ = 'Copyright 2014 Fabian Affolter'
6 | __license__ = 'Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)'
7 |
8 | import dbus
9 |
10 |
11 | def plugin(srv, item):
12 | """Send a message through dbus to the user's desktop."""
13 |
14 | srv.logging.debug("*** MODULE=%s: service=%s, target=%s", __file__, item.service, item.target)
15 |
16 | text = item.message
17 | summary = item.addrs[0]
18 | app_name = item.get('title', srv.SCRIPTNAME)
19 | replaces_id = 0
20 | service = 'org.freedesktop.Notifications'
21 | path = '/' + service.replace('.', '/')
22 | interface = service
23 | app_icon = '/usr/share/icons/gnome/32x32/places/network-server.png'
24 | expire_timeout = 1000
25 | actions = []
26 | hints = []
27 |
28 | try:
29 | srv.logging.debug("Sending message to %s..." % (item.target))
30 | session_bus = dbus.SessionBus()
31 | obj = session_bus.get_object(service, path)
32 | interface = dbus.Interface(obj, interface)
33 | interface.Notify(app_name, replaces_id, app_icon, summary, text,
34 | actions, hints, expire_timeout)
35 | srv.logging.debug("Successfully sent message")
36 | except Exception as e:
37 | srv.logging.error("Error sending message to %s: %s" % (item.target, e))
38 | return False
39 |
40 | return True
41 |
--------------------------------------------------------------------------------
/mqttwarn/services/desktopnotify.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | __author__ = 'Alexander Gräf '
5 | __copyright__ = 'Copyright 2022 Alexander Gräf'
6 | __version__ = '1.0.0'
7 | __license__ = 'Eclipse Public License - v 2.0 - https://www.eclipse.org/legal/epl-2.0/'
8 |
9 | import json
10 | import typing as t
11 |
12 | from desktop_notifier import DesktopNotifier, Urgency, Button, ReplyField
13 |
14 | from mqttwarn.model import Service, ProcessorItem, Struct
15 |
16 | notify = DesktopNotifier()
17 |
18 | def is_json(msg: t.Union[str, bytes]) -> bool:
19 | try:
20 | json.loads(msg)
21 | except ValueError as e:
22 | return False
23 | return True
24 |
25 | def plugin(srv: Service, item: ProcessorItem):
26 | # Log
27 | srv.logging.debug("*** MODULE=%s: service=%s, target=%s", __file__, item.service, item.target)
28 |
29 | # Load Config
30 | config = item.config
31 |
32 | # Play Sound ?
33 | playSound = True
34 | if isinstance(config, dict):
35 | playSound = config.get('sound', True)
36 |
37 | # Get Message
38 | message = item.message
39 | if message and is_json(message):
40 | data = json.loads(message)
41 | else:
42 | data = {
43 | "title" : item.get('title', item.topic),
44 | "message": message
45 | }
46 |
47 | srv.logging.debug("Sending desktop notification")
48 | try:
49 | # Synchronous Notification (allows no callbacks in OSX)
50 | # Asynchronous would require asyncio and require some changes to the plugin handler
51 | notify.send_sync(message=data['message'], title=data['title'],sound=playSound)
52 |
53 | except Exception as e:
54 | srv.logging.warning("Invoking desktop notifier failed: %s" % e)
55 | return False
56 |
57 | return True
58 |
--------------------------------------------------------------------------------
/mqttwarn/services/dnsupdate.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | from builtins import str
5 |
6 | __author__ = 'Jan-Piet Mens '
7 | __copyright__ = 'Copyright 2015 Jan-Piet Mens'
8 | __license__ = 'Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)'
9 |
10 | import dns.update
11 | import dns.query
12 | import dns.tsigkeyring
13 |
14 |
15 | def plugin(srv, item):
16 | srv.logging.debug("*** MODULE=%s: service=%s, target=%s", __file__, item.service, item.target)
17 |
18 | config = item.config
19 |
20 | dns_nameserver = config['dns_nameserver']
21 | dns_keyname = config['dns_keyname']
22 | dns_keyblob = config['dns_keyblob']
23 |
24 | try:
25 | zone, domain, ttl, rrtype = item.addrs
26 |
27 | except:
28 | srv.logging.error("Incorrect target configuration for {0}/{1}".format(item.service, item.target))
29 | return False
30 |
31 | text = item.message
32 |
33 | try:
34 | keyring = dns.tsigkeyring.from_text({str(dns_keyname): str(dns_keyblob)});
35 |
36 | update = dns.update.Update(zone,
37 | keyring=keyring,
38 | keyname=dns_keyname,
39 | keyalgorithm='hmac-sha256') # FIXME configurable
40 |
41 | if rrtype.upper() == 'TXT':
42 | text = '"%s"' % text
43 |
44 | update.replace(domain, ttl, rrtype, text)
45 | response = dns.query.tcp(update, dns_nameserver, timeout=10)
46 |
47 | srv.logging.debug("DNS Update: {0}".format(dns.rcode.to_text(response.rcode())))
48 | except Exception as e:
49 | srv.logging.warning("Cannot notify to dnsupdate `%s': %s" % (dns_nameserver, e))
50 | return False
51 |
52 | return True
53 |
--------------------------------------------------------------------------------
/mqttwarn/services/emoncms.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | __author__ = 'Ben Jones '
5 | __copyright__ = 'Copyright 2014 Ben Jones'
6 | __license__ = 'Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)'
7 |
8 | from future import standard_library
9 | standard_library.install_aliases()
10 | from builtins import str
11 |
12 | import urllib.request, urllib.parse, urllib.error
13 |
14 | try:
15 | import simplejson as json
16 | except ImportError:
17 | import json # type: ignore[no-redef]
18 |
19 |
20 | def plugin(srv, item):
21 | """ addrs: (node, name) """
22 |
23 | srv.logging.debug("*** MODULE=%s: service=%s, target=%s", __file__, item.service, item.target)
24 |
25 | url = item.config['url']
26 | apikey = item.config['apikey']
27 | timeout = item.config['timeout']
28 |
29 | node = item.addrs[0]
30 | name = item.addrs[1]
31 | value = item.payload
32 |
33 | try:
34 | params = { 'apikey': apikey, 'node': node, 'json': json.dumps({ name : value }) }
35 | resource = url + '/input/post.json?' + urllib.parse.urlencode(params)
36 |
37 | request = urllib.request.Request(resource)
38 | request.add_header('User-agent', srv.SCRIPTNAME)
39 |
40 | response = urllib.request.urlopen(request, timeout=timeout)
41 | data = response.read()
42 | except Exception as e:
43 | srv.logging.warn("Failed to send GET request to EmonCMS using %s: %s" % (resource, e))
44 | return False
45 |
46 | return True
47 |
--------------------------------------------------------------------------------
/mqttwarn/services/execute.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | __author__ = 'Tobias Brunner '
4 | __copyright__ = 'Copyright 2016 Tobias Brunner'
5 | __license__ = """Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)"""
6 |
7 | import subprocess
8 |
9 | def plugin(srv, item):
10 |
11 | srv.logging.debug("*** MODULE=%s: service=%s, target=%s", __file__, item.service, item.target)
12 |
13 | config = item.config
14 | if type(config) == dict and 'text_replace' in config:
15 | replace = config['text_replace']
16 | else:
17 | replace = '[TEXT]'
18 |
19 | text = item.message
20 | cmd = [i.replace(replace, text) for i in item.addrs]
21 |
22 | try:
23 | subprocess.check_call(cmd, stdin=None, stderr=subprocess.STDOUT, shell=False, universal_newlines=True, cwd='/tmp')
24 | except Exception as e:
25 | srv.logging.warning("Cannot execute %s because %s" % (cmd, e))
26 | return False
27 |
28 | return True
29 |
--------------------------------------------------------------------------------
/mqttwarn/services/fbchat.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | __author__ = 'Przemek Anuszek '
5 | __copyright__ = 'Copyright 2016 Przemek Anuszek'
6 | __license__ = 'Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)'
7 |
8 | from fbchat import Client
9 | from fbchat.models import *
10 |
11 |
12 | def plugin(srv, item):
13 |
14 | srv.logging.debug("*** MODULE=%s: service=%s, target=%s", __file__, item.service, item.target)
15 |
16 | client = item.addrs[0]
17 | password = item.addrs[1]
18 | friend = item.addrs[2]
19 |
20 | fbclient = Client(client, password)
21 | friends = fbclient.searchForUsers(friend)
22 | ffriend = friends[0]
23 |
24 | srv.logging.debug("user %s" % (item.target))
25 |
26 | text = item.message
27 | try:
28 | srv.logging.debug("Sending msg to %s..." % (item.target))
29 | sent = fbclient.sendMessage(text, thread_id=ffriend.uid, thread_type=ThreadType.USER)
30 | srv.logging.debug("Successfully sent message")
31 | except Exception as e:
32 | srv.logging.error("Error sending fbchat to %s: %s" % (item.target, e))
33 | return False
34 | client.logout()
35 | return True
36 |
--------------------------------------------------------------------------------
/mqttwarn/services/file.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | __author__ = 'Jan-Piet Mens '
5 | __copyright__ = 'Copyright 2014 Jan-Piet Mens'
6 | __license__ = 'Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)'
7 |
8 | import io
9 | import tempfile
10 |
11 |
12 | def plugin(srv, item):
13 |
14 | srv.logging.debug("*** MODULE=%s: service=%s, target=%s", __file__, item.service, item.target)
15 |
16 | mode = "a"
17 |
18 | # item.config is brought in from the configuration file
19 | config = item.config
20 |
21 | # Evaluate global parameters.
22 | newline = False
23 | overwrite = False
24 | if type(config) == dict and 'append_newline' in config and config['append_newline']:
25 | newline = True
26 | if type(config) == dict and 'overwrite' in config and config['overwrite']:
27 | overwrite = True
28 |
29 | # `item.addrs` is either a dict or a list associated with a particular target.
30 | # While lists may contain more than one item (e.g., for the pushover target),
31 | # the `file` service only allows for single items, the path name.
32 | # When it's a dict, additional parameters can be obtained to augment the
33 | # behavior of the write operation on a per-file basis.
34 | if isinstance(item.addrs, dict):
35 | filename = item.addrs['path'].format(**item.data)
36 | # Evaluate per-file parameters.
37 | newline = item.addrs.get('append_newline', newline)
38 | overwrite = item.addrs.get('overwrite', overwrite)
39 | else:
40 | filename = item.addrs[0].format(**item.data)
41 |
42 | # Interpolate some variables into filename.
43 | if "$TMPDIR" in filename:
44 | filename = filename.replace("$TMPDIR", tempfile.gettempdir())
45 |
46 | srv.logging.info("Writing to file `%s'" % (filename))
47 |
48 | # If the incoming payload has been transformed, use that,
49 | # else the original payload
50 | text = item.message
51 |
52 | if newline:
53 | text += "\n"
54 | if overwrite:
55 | mode = "w"
56 |
57 | if isinstance(text, bytes):
58 | mode += "b"
59 | encoding = None
60 | else:
61 | encoding = "utf-8"
62 |
63 | try:
64 | f = io.open(filename, mode=mode, encoding=encoding)
65 | f.write(text)
66 | f.close()
67 |
68 | except Exception as e:
69 | srv.logging.error("Cannot write to file `%s': %s" % (filename, e))
70 | return False
71 |
72 | return True
73 |
--------------------------------------------------------------------------------
/mqttwarn/services/freeswitch.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | __author__ = 'Ben Jones '
5 | __copyright__ = 'Copyright 2014 Ben Jones'
6 | __license__ = 'Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)'
7 |
8 | from future import standard_library
9 | standard_library.install_aliases()
10 |
11 | from xmlrpc.client import ServerProxy
12 | import urllib.request, urllib.parse, urllib.error
13 |
14 |
15 | def plugin(srv, item):
16 |
17 | srv.logging.debug("*** MODULE=%s: service=%s, target=%s", __file__, item.service, item.target)
18 |
19 | host = item.config['host']
20 | port = item.config['port']
21 | username = item.config['username']
22 | password = item.config['password']
23 | ttsurl = item.config['ttsurl']
24 | ttsparams = item.config['ttsparams']
25 |
26 | gateway = item.addrs[0]
27 | number = item.addrs[1]
28 | title = item.title
29 |
30 | if ttsurl.startswith('http://'):
31 | ttsurl = ttsurl[7:]
32 | elif ttsurl.startswith('https://'):
33 | ttsurl = ttsurl[8:]
34 |
35 | if ttsparams is not None:
36 | for key in list(ttsparams.keys()):
37 |
38 | # { 'q' : '@message' }
39 | # Quoted field, starts with '@'. Do not use .format, instead grab
40 | # the item's [message] and inject as parameter value.
41 | if ttsparams[key].startswith('@'): # "@message"
42 | ttsparams[key] = item.get(ttsparams[key][1:], "NOP")
43 |
44 | else:
45 | try:
46 | ttsparams[key] = ttsparams[key].format(**item.data).encode('utf-8')
47 | except Exception as e:
48 | srv.logging.debug("Parameter %s cannot be formatted: %s" % (key, e))
49 | return False
50 |
51 | try:
52 | # TTS service
53 | shout_url = "shout://%s" % ttsurl
54 | if ttsparams is not None:
55 | if not shout_url.endswith('?'):
56 | shout_url = shout_url + '?'
57 | shout_url = shout_url + urllib.parse.urlencode(ttsparams)
58 | # debugging
59 | srv.logging.debug("Shout URL: %s" % shout_url)
60 | # Freeswitch API
61 | server = ServerProxy("http://%s:%s@%s:%d" % (username, password, host, port))
62 | # channel variables we need to setup the call
63 | channel_vars = "{ignore_early_media=true,originate_timeout=60,origination_caller_id_name='" + title + "'}"
64 | # originate the call
65 | server.freeswitch.api("originate", channel_vars + gateway + number + " &playback(" + shout_url + ")")
66 | except Exception as e:
67 | srv.logging.error("Error originating Freeswitch VOIP call to %s via %s%s: %s" % (item.target, gateway, number, e))
68 | return False
69 |
70 | return True
71 |
--------------------------------------------------------------------------------
/mqttwarn/services/hangbot.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | """
4 | __author__ = 'Bram Hendrickx'
5 | __copyright__ = 'Copyright 2016 Bram Hendrickx'
6 | __license__ = 'Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)'
7 |
8 | Original script by Bram Hendrickx. https://github.com/mqtt-tools/mqttwarn/blob/main/mqttwarn/services/ifttt.py
9 | Modified to work with hangoutsbot api plugin https://github.com/hangoutsbot/hangoutsbot/wiki/API-Plugin
10 | """
11 |
12 | __author__ = 'Michael Brougham'
13 | __copyright__ = 'Copyright 2018 Michael Brougham'
14 | __license__ = 'Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)'
15 |
16 |
17 | import json
18 | import requests
19 |
20 |
21 | def plugin(srv, item):
22 | """ expects (url,port,apikey, convid) in addrs """
23 |
24 | srv.logging.debug("*** MODULE=%s: service=%s, target=%s", __file__, item.service, item.target)
25 |
26 | payload = {
27 | 'key': item.addrs[2],
28 | 'sendto': item.addrs[3],
29 | 'content': item.message
30 | }
31 | try:
32 | srv.logging.debug("Sending to hangoutsbot")
33 | url = "https://" + item.addrs[0] + ":" + item.addrs[1]
34 | headers = {'content-type': 'application/json'}
35 | requests.post(url, data = json.dumps(payload), headers = headers, verify=False)
36 | srv.logging.debug("Successfully sent to hangoutsbot")
37 | except Exception as e:
38 | srv.logging.warning("Failed to send message to hangoutsbot" % e)
39 | return False
40 |
41 | return True
42 |
--------------------------------------------------------------------------------
/mqttwarn/services/icinga2.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | __author__ = 'Ben Jones '
5 | __copyright__ = 'Copyright 2016 Ben Jones'
6 | __license__ = 'Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)'
7 |
8 | import requests
9 | from requests.auth import HTTPBasicAuth
10 |
11 | try:
12 | import simplejson as json
13 | except ImportError:
14 | import json # type: ignore[no-redef]
15 |
16 |
17 | def plugin(srv, item):
18 | srv.logging.debug("*** MODULE=%s: service=%s, target=%s", __file__, item.service, item.target)
19 |
20 | host = item.config['host']
21 | port = item.config['port']
22 | username = item.config['username']
23 | password = item.config['password']
24 |
25 | # optional ca-cert (usually self-signed cert installed by icinga2)
26 | if 'cacert' in item.config:
27 | cacert = item.config['cacert']
28 | else:
29 | cacert = None
30 |
31 | # e.g. example.com!ping4
32 | check_host = item.addrs[0]
33 | check_service = item.addrs[1]
34 | check_source = item.addrs[2]
35 |
36 | if check_service is None:
37 | check_type = 'host'
38 | check_target = check_host
39 | else:
40 | check_type = 'service'
41 | check_target = '{0}!{1}'.format(check_host, check_service)
42 |
43 | if check_source is None:
44 | check_source = 'mqttwarn'
45 |
46 | payload = {
47 | 'exit_status': item.priority,
48 | 'plugin_output': item.message,
49 | 'check_source': check_source,
50 | check_type: check_target,
51 | }
52 |
53 | # Update our payload with any JSON data in the message.
54 | try:
55 | payload.update(json.loads(item.message))
56 | except Exception:
57 | pass
58 |
59 | # Request parameters
60 | headers = {
61 | "Accept": "application/json"
62 | }
63 |
64 | kwargs = {
65 | "headers": headers,
66 | "auth": HTTPBasicAuth(username, password),
67 | "json": payload
68 | }
69 |
70 | if cacert:
71 | kwargs["verify"] = cacert
72 |
73 | try:
74 | url = "%s:%d/v1/actions/process-check-result" % (host, port)
75 | r = requests.post(url, **kwargs)
76 | if r.status_code != requests.codes.ok:
77 | srv.logging.warning("Invalid response from icinga2 REST API at `%s`: %s" % (host, r.text))
78 | return False
79 | except Exception as e:
80 | srv.logging.warning("Failed to POST request to icinga2 REST API at `%s': %s" % (host, e))
81 | return False
82 |
83 | return True
84 |
--------------------------------------------------------------------------------
/mqttwarn/services/ifttt.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | __author__ = 'Bram Hendrickx'
5 | __copyright__ = 'Copyright 2016 Bram Hendrickx'
6 | __license__ = 'Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)'
7 |
8 | import requests
9 |
10 |
11 | def plugin(srv, item):
12 | ''' expects (apikey, event) in adddrs '''
13 |
14 | srv.logging.debug("*** MODULE=%s: service=%s, target=%s", __file__, item.service, item.target)
15 |
16 | event_type = "device_iden"
17 | try:
18 | apikey, event = item.addrs
19 | except:
20 | try:
21 | apikey, event = item.addrs
22 | except:
23 | srv.logging.warn("ifttt target is incorrectly configured")
24 | return False
25 |
26 | payload = {}
27 | payload["value1"] = item.message
28 |
29 | try:
30 | srv.logging.debug("Sending ifttt event")
31 | url = "https://maker.ifttt.com/trigger/" + event + "/with/key/" + apikey
32 | requests.post(url, data=payload)
33 | srv.logging.debug("Successfully sent ifttt event")
34 | except Exception as e:
35 | srv.logging.warning("Cannot send ifttt event: %s" % e)
36 | return False
37 |
38 | return True
39 |
--------------------------------------------------------------------------------
/mqttwarn/services/irccat.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | __author__ = 'Jan-Piet Mens '
5 | __copyright__ = 'Copyright 2014 Jan-Piet Mens'
6 | __license__ = 'Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)'
7 |
8 | import socket
9 |
10 |
11 | def plugin(srv, item):
12 | srv.logging.debug("*** MODULE=%s: service=%s, target=%s", __file__, item.service, item.target)
13 |
14 | try:
15 | addr, port, channel = item.addrs
16 | except:
17 | srv.logging.warning("Incorrect target configuration")
18 | return False
19 |
20 | message = item.message
21 |
22 | # Optionally apply coloring.
23 | color = None
24 | if item.priority == 1:
25 | color = '%GREEN'
26 | elif item.priority == 2:
27 | color = '%RED'
28 | if color is not None:
29 | message = color + message
30 |
31 | srv.logging.debug("Sending to IRCcat: %s" % (message))
32 |
33 | # Apparently, a trailing newline is needed.
34 | # https://github.com/mqtt-tools/mqttwarn/issues/547#issuecomment-944632712
35 | message += "\n"
36 |
37 | try:
38 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
39 | sock.connect((addr, port))
40 | sock.send(message.encode())
41 | sock.close()
42 |
43 | except Exception as e:
44 | srv.logging.error("Error sending IRCcat notification to %s:%s [%s]: %s" % (item.target, addr, port, e))
45 | return False
46 |
47 | return True
48 |
--------------------------------------------------------------------------------
/mqttwarn/services/linuxnotify.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | __author__ = 'Fabian Affolter '
5 | __copyright__ = 'Copyright 2014 Fabian Affolter'
6 | __license__ = 'Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)'
7 |
8 | from gi.repository import Notify
9 |
10 |
11 | def plugin(srv, item):
12 | """Send a message to the user's desktop notification system."""
13 |
14 | srv.logging.debug("*** MODULE=%s: service=%s, target=%s", __file__,
15 | item.service, item.target)
16 |
17 | title = item.addrs[0]
18 | text = item.message
19 |
20 | try:
21 | srv.logging.debug("Sending notification to the user's desktop")
22 | Notify.init('mqttwarn')
23 | n = Notify.Notification.new(
24 | title,
25 | text,
26 | '/usr/share/icons/gnome/32x32/places/network-server.png')
27 | n.show()
28 | srv.logging.debug("Successfully sent notification")
29 | except Exception as e:
30 | srv.logging.warning("Cannot invoke notification to linux: %s" % e)
31 | return False
32 |
33 | return True
34 |
--------------------------------------------------------------------------------
/mqttwarn/services/log.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | __author__ = 'Jan-Piet Mens '
5 | __copyright__ = 'Copyright 2014 Jan-Piet Mens'
6 | __license__ = 'Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)'
7 |
8 |
9 | def plugin(srv, item):
10 | srv.logging.debug("*** MODULE=%s: service=%s, target=%s", __file__, item.service, item.target)
11 |
12 | assert isinstance(item.addrs, list), "`item.addrs` is not a list"
13 |
14 | level = item.addrs[0]
15 |
16 | text = item.message
17 |
18 | levels = {
19 | 'debug': srv.logging.debug,
20 | 'info': srv.logging.info,
21 | 'warn': srv.logging.warning,
22 | 'crit': srv.logging.critical,
23 | 'error': srv.logging.error,
24 | }
25 |
26 | try:
27 | levels[level]("%s", text)
28 | except Exception as e:
29 | srv.logging.error("Cannot invoke service log with level `%s': %s" % (level, e))
30 | return False
31 |
32 | return True
33 |
--------------------------------------------------------------------------------
/mqttwarn/services/mattermost.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | __author__ = 'Jan-Piet Mens '
5 | __copyright__ = 'Copyright 2018 Jan-Piet Mens'
6 | __license__ = 'Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)'
7 |
8 | from six import string_types
9 |
10 | import requests
11 |
12 | try:
13 | import simplejson as json
14 | except ImportError:
15 | import json # type: ignore[no-redef]
16 |
17 |
18 | def plugin(srv, item):
19 | srv.logging.debug("*** MODULE=%s: service=%s, target=%s", __file__, item.service, item.target)
20 |
21 | hook_url = item.addrs[0]
22 | channel = item.addrs[1]
23 | username = item.addrs[2] # may be None
24 | icon_url = item.addrs[3] # may be None
25 |
26 | text = item.message
27 | try:
28 | """ Try to format a Markdown table if we have JSON in the payload """
29 | """ ETOOMESSY; volunteers to refactor? """
30 | title = item.get('title', None)
31 | j = json.loads(text)
32 | keylen = vallen = 10
33 | for key in j:
34 | # print type(key), keylen, len(key)
35 | if isinstance(key, string_types) and keylen < len(key):
36 | keylen = len(key)
37 | if isinstance(j[key], string_types) and vallen < len(j[key]):
38 | vallen = len(j[key])
39 | s = ""
40 | if title is not None and title != "":
41 | s = "## %s\n" % title
42 | key = "key"
43 | val = "value"
44 | s = s + "| {0:<{kw}} | {1:<{vw}} |\n".format("key", "value", kw=keylen, vw=vallen)
45 | s = s + "|:{0:<{kw}} |:{1:<{vw}} |\n".format('-' * keylen, '-' * vallen, kw=keylen, vw=vallen)
46 | for key in j:
47 | s = s + "| {0:<{kw}} | {1:<{vw}} |\n".format(key, j[key], kw=keylen, vw=vallen)
48 | text = s
49 | except Exception as e:
50 | srv.logging.debug("not JSON; proceeding with text")
51 | pass
52 |
53 | payload = {}
54 | payload["channel"] = channel
55 | payload["text"] = text
56 | if username is not None:
57 | payload["username"] = username
58 | if icon_url is not None:
59 | payload["icon_url"] = icon_url
60 |
61 | # print payload
62 |
63 | headers = {
64 | "Content-type": "application/json",
65 | "Accept": "application/json"
66 | }
67 |
68 | try:
69 | r = requests.post(hook_url, data=json.dumps(payload), headers=headers)
70 | if r.status_code != requests.codes.ok:
71 | srv.logging.warning("Invalid response from Mattermost Webhook: %s" % (r.text))
72 | return False
73 | except Exception as e:
74 | srv.logging.warning("Failed to POST request to Mattermost Webhook: %s" % e)
75 | return False
76 |
77 | return True
78 |
--------------------------------------------------------------------------------
/mqttwarn/services/mqtt_filter.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | __author__ = 'Joerg Gollnick '
5 | __copyright__ = 'Copyright 2016 Tobias Brunner / 2021 Joerg Gollnick'
6 | __license__ = """Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)"""
7 |
8 | import subprocess
9 | import json
10 | from pipes import quote
11 | from six import string_types
12 |
13 | def plugin(srv, item):
14 |
15 | srv.logging.debug("*** MODULE=%s: service=%s, target=%s", __file__, item.service, item.target)
16 |
17 | # same as for ssh
18 | json_message=json.loads(item.message)
19 |
20 | args = None
21 | if json_message is not None:
22 | args = json_message["args"]
23 |
24 | if type(args) is list and len(args) == 1:
25 | args=args[0]
26 |
27 | if type(args) is list:
28 | args=tuple([ quote(v) for v in args ]) #escape the shell args
29 | elif type(args) is str or type(args) is unicode:
30 | args=(quote(args),)
31 |
32 | # parse topic
33 | topic=list(map( lambda x: quote(x), item.topic.split('/') ))
34 |
35 | outgoing_topic = None
36 | # replace palceholders args[0], args[1] ..., full_topic, topic[0],
37 | if item.addrs[0] is not None:
38 | outgoing_topic = item.addrs[0].format(args=args,full_topic=quote(item.topic),topic=topic)
39 | qos = item.addrs[1]
40 | retain = item.addrs[2]
41 | addrs = item.addrs[3:]
42 |
43 | cmd = None
44 | if addrs is not None:
45 | cmd = [i.format(args=args, full_topic=quote(item.topic),topic=topic) for i in addrs]
46 |
47 | srv.logging.debug("*** MODULE=%s: service=%s, command=%s outgoing_topic=%s", __file__, item.service, str( cmd ),outgoing_topic)
48 |
49 | try:
50 | res = subprocess.check_output(cmd, stdin=None, stderr=subprocess.STDOUT, shell=False, universal_newlines=True, cwd='/tmp')
51 | except Exception as e:
52 | srv.logging.warning("Cannot execute %s because %s" % (cmd, e))
53 | return False
54 |
55 | if outgoing_topic is not None:
56 | outgoing_payload = res.rstrip('\n')
57 | if isinstance(outgoing_payload, string_types):
58 | outgoing_payload = bytearray(outgoing_payload, encoding='utf-8')
59 | try:
60 | srv.mqttc.publish(outgoing_topic, outgoing_payload, qos=qos, retain=retain)
61 | except Exception as e:
62 | srv.logging.warning("Cannot PUBlish response %s: %s" % (outgoing_topic, e))
63 | return False
64 |
65 | return True
66 |
--------------------------------------------------------------------------------
/mqttwarn/services/mqttpub.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | from six import string_types
5 |
6 | __author__ = 'Jan-Piet Mens '
7 | __copyright__ = 'Copyright 2014 Jan-Piet Mens'
8 | __license__ = 'Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)'
9 |
10 |
11 | def plugin(srv, item):
12 | """ Publish via MQTT to the same broker connection.
13 | Requires topic, qos and retain flag """
14 |
15 | srv.logging.debug("*** MODULE=%s: service=%s, target=%s", __file__, item.service, item.target)
16 |
17 | outgoing_topic = item.addrs[0]
18 | qos = item.addrs[1]
19 | retain = item.addrs[2]
20 |
21 | # Attempt to interpolate data into topic name. If it isn't possible
22 | # ignore, and return without publish
23 |
24 | if item.data is not None:
25 | try:
26 | outgoing_topic = item.addrs[0].format(**item.data)
27 | except:
28 | srv.logging.debug("Outgoing topic cannot be formatted; not published")
29 | return False
30 |
31 | outgoing_payload = item.message
32 | if isinstance(outgoing_payload, string_types):
33 | outgoing_payload = bytearray(outgoing_payload, encoding='utf-8')
34 |
35 | try:
36 | srv.mqttc.publish(outgoing_topic, outgoing_payload, qos=qos, retain=retain)
37 | except Exception as e:
38 | srv.logging.warning("Cannot PUBlish via `mqttpub:%s': %s" % (item.target, e))
39 | return False
40 |
41 | return True
42 |
--------------------------------------------------------------------------------
/mqttwarn/services/mythtv.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | __author__ = 'Stefan Roellin '
5 | __copyright__ = 'Copyright 2015 Stefan Roellin'
6 | __license__ = 'Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)'
7 |
8 | from future import standard_library
9 | standard_library.install_aliases()
10 |
11 | import http.client, urllib.request, urllib.parse, urllib.error
12 |
13 |
14 | def plugin(srv, item):
15 |
16 | srv.logging.debug("*** MODULE=%s: service=%s, target=%s", __file__, item.service, item.target)
17 |
18 | data = {'Message': item.message.encode('utf-8'),
19 | 'Origin': item.title,
20 | 'Timeout': item.config.get('timeout', 5),
21 | 'Address': item.addrs[1],
22 | 'Progress': -1
23 | }
24 | if item.image != None:
25 | data['Image'] = item.image
26 |
27 | http_handler = http.client.HTTPConnection(item.addrs[0])
28 |
29 | try:
30 | http_handler.request("POST", 'Myth/SendNotification',
31 | headers={'Content-type': "application/x-www-form-urlencoded", "Accept": "text/plain"},
32 | body=urllib.parse.urlencode(data))
33 | except (SSLError, HTTPException) as e:
34 | srv.logging.warn("mythtv notification failed: %s" % e)
35 | return False
36 |
37 | response = http_handler.getresponse()
38 |
39 | srv.logging.debug("Reponse: %s, %s" % (response.status, response.reason))
40 |
41 | return True
42 |
--------------------------------------------------------------------------------
/mqttwarn/services/nntp.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | __author__ = 'Jan-Piet Mens '
5 | __copyright__ = 'Copyright 2014 Jan-Piet Mens'
6 | __license__ = 'Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)'
7 |
8 | import nntplib
9 | from six import StringIO
10 | from email.mime.text import MIMEText
11 | from email.utils import formatdate
12 |
13 |
14 | def plugin(srv, item):
15 |
16 | srv.logging.debug("*** MODULE=%s: service=%s, target=%s", __file__, item.service, item.target)
17 |
18 | host = item.config.get('server', 'localhost')
19 | port = item.config.get('port', 119)
20 | username = item.config.get('username')
21 | password = item.config.get('password')
22 |
23 | try:
24 | from_hdr = item.addrs[0]
25 | newsgroup = item.addrs[1]
26 | except Exception:
27 | srv.logging.error("Incorrect target configuration for %s" % item.target)
28 | return False
29 |
30 | try:
31 |
32 | text = item.message
33 | title = item.title
34 |
35 | msg = MIMEText(text)
36 |
37 | msg['From'] = from_hdr
38 | msg['Subject'] = item.title
39 | msg['Newsgroups'] = newsgroup
40 | msg['Date'] = formatdate()
41 | msg['User-Agent'] = srv.SCRIPTNAME
42 | # msg['Message-ID'] = ''
43 |
44 | msg_file = StringIO(msg.as_string())
45 | nntp = nntplib.NNTP(host, port, user=username, password=password)
46 |
47 | srv.logging.debug(nntp.getwelcome())
48 | nntp.set_debuglevel(0)
49 |
50 | nntp.post(msg_file)
51 | nntp.quit()
52 | except Exception as e:
53 | srv.logging.warn("Cannot post to %s newsgroup: %s" % (newsgroup, e))
54 | return False
55 |
56 | return True
57 |
--------------------------------------------------------------------------------
/mqttwarn/services/noop.py:
--------------------------------------------------------------------------------
1 | from mqttwarn.model import Service, ProcessorItem
2 |
3 |
4 | def plugin(srv: Service, item: ProcessorItem) -> bool:
5 | """
6 | An mqttwarn plugin with little overhead, suitable for unit- and integration-testing.
7 | """
8 | if hasattr(item, "message") and item.message and "fail" in item.message:
9 | srv.logging.error("Failed sending message using noop")
10 | return False
11 | else:
12 | srv.logging.info("Successfully sent message using noop")
13 | return True
14 |
--------------------------------------------------------------------------------
/mqttwarn/services/nsca.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | __author__ = 'Jan-Piet Mens '
5 | __copyright__ = 'Copyright 2014 Jan-Piet Mens'
6 | __license__ = 'Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)'
7 |
8 |
9 | import pynsca
10 | from pynsca import NSCANotifier
11 |
12 |
13 | def plugin(srv, item):
14 |
15 | srv.logging.debug("*** MODULE=%s: service=%s, target=%s", __file__, item.service, item.target)
16 |
17 | config = item.config
18 |
19 | statii = [ pynsca.OK, pynsca.WARNING, pynsca.CRITICAL, pynsca.UNKNOWN ]
20 | status = pynsca.OK
21 | try:
22 | prio = item.priority
23 | status = statii[prio]
24 | except:
25 | pass
26 |
27 | nsca_host = str(config['nsca_host'])
28 |
29 | host_name = item.addrs[0]
30 | service_description = item.addrs[1]
31 |
32 | # If the incoming payload has been transformed, use that,
33 | # else the original payload
34 | text = item.message
35 |
36 | try:
37 | notif = NSCANotifier(nsca_host)
38 | notif.svc_result(host_name, service_description, status, text)
39 | except Exception as e:
40 | srv.logging.warning("Cannot notify to NSCA host `%s': %s" % (nsca_host, e))
41 | return False
42 |
43 | return True
44 |
--------------------------------------------------------------------------------
/mqttwarn/services/osxsay.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | __author__ = 'Jan-Piet Mens '
5 | __copyright__ = 'Copyright 2014 Jan-Piet Mens'
6 | __license__ = 'Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)'
7 |
8 | import subprocess
9 |
10 |
11 | def plugin(srv, item):
12 |
13 | srv.logging.debug("*** MODULE=%s: service=%s, target=%s", __file__, item.service, item.target)
14 |
15 | voice = item.addrs[0]
16 | text = item.message
17 |
18 | argv = [ "/usr/bin/say", "-f", "-", "--voice=%s" % voice ]
19 |
20 | try:
21 | proc = subprocess.Popen(argv,
22 | stdin=subprocess.PIPE, close_fds=True)
23 | except Exception as e:
24 | srv.logging.warn("Cannot create osxsay pipe: %s" % e)
25 | return False
26 |
27 | try:
28 | proc.stdin.write(text)
29 | except IOError as e:
30 | srv.logging.warn("Cannot write to osxsay pipe: errno %d" % (e.errno))
31 | return False
32 | except Exception as e:
33 | srv.logging.warn("Cannot write to osxsay pipe: %s" % e)
34 | return False
35 |
36 | proc.stdin.close()
37 | proc.wait()
38 | return True
39 |
--------------------------------------------------------------------------------
/mqttwarn/services/pastebinpub.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | __author__ = 'Fabian Affolter '
5 | __copyright__ = 'Copyright 2014 Fabian Affolter'
6 | __license__ = 'Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)'
7 |
8 | from pastebin import PastebinAPI
9 |
10 |
11 | def plugin(srv, item):
12 | """ Pushlish the message to pastebin.com """
13 |
14 | srv.logging.debug("*** MODULE=%s: service=%s, target=%s", __file__,
15 | item.service, item.target)
16 |
17 | pastebin_data = item.addrs
18 |
19 | pastebinapi = PastebinAPI()
20 | api_dev_key = pastebin_data[0]
21 | username = pastebin_data[1]
22 | password = pastebin_data[2]
23 | pastename = 'mqttwarn'
24 | pasteprivate = pastebin_data[3]
25 | expiredate = pastebin_data[4]
26 |
27 | text = item.message
28 |
29 | try:
30 | api_user_key = pastebinapi.generate_user_key(
31 | api_dev_key,
32 | username,
33 | password)
34 | except Exception as e:
35 | srv.logging.warn("Cannot retrieve session data from pastebin: %s" % e)
36 | return False
37 |
38 | try:
39 | srv.logging.debug("Adding entry to pastebin.com as user %s..." % (username))
40 | pastebinapi.paste(
41 | api_dev_key,
42 | text,
43 | api_user_key = api_user_key,
44 | paste_name = pastename,
45 | paste_format = None,
46 | paste_private = pasteprivate,
47 | paste_expire_date = expiredate
48 | )
49 | srv.logging.debug("Successfully added paste to pastebin")
50 | except Exception as e:
51 | srv.logging.warn("Cannot publish to pastebin: %s" % e)
52 | return False
53 |
54 | return True
55 |
--------------------------------------------------------------------------------
/mqttwarn/services/pipe.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | __author__ = 'Jan-Piet Mens '
5 | __copyright__ = 'Copyright 2014 Jan-Piet Mens'
6 | __license__ = 'Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)'
7 |
8 | import errno
9 | import subprocess
10 |
11 |
12 | def plugin(srv, item):
13 |
14 | srv.logging.debug("*** MODULE=%s: service=%s, target=%s", __file__, item.service, item.target)
15 |
16 | # addrs is a list[] with process name and args
17 | argv = item.addrs
18 |
19 | text = item.message
20 |
21 | if not text.endswith("\n"):
22 | text = text + "\n"
23 |
24 | try:
25 | proc = subprocess.Popen(argv,
26 | stdin=subprocess.PIPE, close_fds=True)
27 | except Exception as e:
28 | srv.logging.warn("Cannot create pipe: %s" % e)
29 | return False
30 |
31 | try:
32 | proc.stdin.write(text.encode('utf-8'))
33 | except IOError as e:
34 | srv.logging.warn("Cannot write to pipe: errno %d" % (e.errno))
35 | return False
36 | except Exception as e:
37 | srv.logging.warn("Cannot write to pipe: %s" % e)
38 | return False
39 |
40 | proc.stdin.close()
41 | proc.wait()
42 | return True
43 |
--------------------------------------------------------------------------------
/mqttwarn/services/prowl.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | __author__ = 'Jan-Piet Mens '
5 | __copyright__ = 'Copyright 2014-2021 Jan-Piet Mens'
6 | __license__ = 'Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)'
7 |
8 | import pyprowl
9 |
10 |
11 | def plugin(srv, item):
12 |
13 | srv.logging.debug("*** MODULE=%s: service=%s, target=%s", __file__, item.service, item.target)
14 |
15 | apikey = item.addrs[0]
16 | application = item.addrs[1]
17 |
18 | title = item.get('title', srv.SCRIPTNAME)
19 | text = item.message
20 | priority = int(item.get('priority', 0))
21 |
22 | try:
23 | p = pyprowl.Prowl(apikey)
24 | p.verify_key()
25 | srv.logging.info("Prowl API key successfully verified")
26 | except Exception as e:
27 | srv.logging.error("Error verifying Prowl API key: {}".format(e))
28 | return False
29 |
30 | try:
31 | p.notify(event=title, description=text,
32 | priority=priority, url=None,
33 | appName=application)
34 | srv.logging.debug("Sending notification to Prowl succeeded")
35 | except Exception as e:
36 | srv.logging.warning("Sending notification to Prowl failed: %s" % e)
37 | return False
38 |
39 | return True
40 |
--------------------------------------------------------------------------------
/mqttwarn/services/redispub.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | __author__ = 'Jan-Piet Mens '
5 | __copyright__ = 'Copyright 2014 Jan-Piet Mens'
6 | __license__ = 'Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)'
7 |
8 | import redis
9 |
10 |
11 | def plugin(srv, item):
12 | """ redispub. Expects addrs to contain (channel) """
13 |
14 | srv.logging.debug("*** MODULE=%s: service=%s, target=%s", __file__, item.service, item.target)
15 |
16 | host = item.config.get('host', 'localhost')
17 | port = int(item.config.get('port', 6379))
18 |
19 | try:
20 | rp = redis.Redis(host, port)
21 | except Exception as e:
22 | srv.logging.warn("Cannot connect to redis on %s:%s : %s" % (host, port, e))
23 | return False
24 |
25 | channel = item.addrs[0]
26 | text = item.message
27 |
28 | try:
29 | rp.publish(channel, text)
30 | except Exception as e:
31 | srv.logging.warn("Cannot publish to redis on %s:%s : %s" % (host, port, e))
32 | return False
33 |
34 | return True
35 |
--------------------------------------------------------------------------------
/mqttwarn/services/rrdtool.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | __author__ = 'devsaurus '
5 | __copyright__ = 'Copyright 2015'
6 | __license__ = 'Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)'
7 |
8 | import re
9 | import rrdtool
10 |
11 |
12 | def plugin(srv, item):
13 |
14 | srv.logging.debug("*** MODULE=%s: service=%s, target=%s", __file__, item.service, item.target)
15 |
16 | # If the incoming payload has been transformed, use that,
17 | # else the original payload
18 | text = item.message
19 |
20 | try:
21 | # addrs is a list[] associated with a particular target.
22 | # it can contain an arbitrary amount of entries that are just
23 | # passed along to rrdtool
24 | # mofified by otfdr @ github to accept abitray arguments with
25 | # the payload and to not always add the 'N' in front
26 | # 2017-06-05 - fix/enhancement for https://github.com/mqtt-tools/mqttwarn/issues/248
27 | if re.match( "^\d+$", text ):
28 | rrdtool.update(item.addrs, "N:" + text)
29 | else:
30 | rrdtool.update(item.addrs + text.split())
31 | except Exception as e:
32 | srv.logging.warning("Cannot call rrdtool")
33 | return False
34 |
35 | return True
36 |
--------------------------------------------------------------------------------
/mqttwarn/services/serial.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | __author__ = 'Daniel Lindner '
5 | __copyright__ = 'Copyright 2016 Daniel Lindner'
6 | __license__ = 'Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)'
7 |
8 | import serial
9 | from builtins import bytes
10 |
11 | _serialport = None
12 |
13 |
14 | def plugin(srv, item):
15 | global _serialport
16 | srv.logging.debug("*** MODULE=%s: service=%s, target=%s", __file__, item.service, item.target)
17 |
18 | # item.config is brought in from the configuration file
19 | config = item.config
20 |
21 | # addrs is a list[] associated with a particular target.
22 | # While it may contain more than one item (e.g. pushover)
23 | # the `serial' service carries one two, i.e. a com name and baudrate
24 | try:
25 | comName = item.addrs[0].format(**item.data)
26 | comBaudRate = int(item.addrs[1])
27 | except:
28 | srv.logging.error("Incorrect target configuration for {0}/{1}".format(item.service, item.target))
29 | return False
30 |
31 | # If the incoming payload has been transformed, use that,
32 | # else the original payload
33 | text = item.message
34 |
35 | # If message specifies the hex keyword try to transform bytes from hex
36 | # else send string as it is
37 | test = text[:5]
38 | if test == ":HEX:":
39 | text = bytes(bytearray.fromhex(text[5:]))
40 |
41 | # Append newline if config option is set
42 | if type(config) == dict and 'append_newline' in config and config['append_newline']:
43 | text = text + "\n"
44 |
45 | try:
46 | try:
47 | if callable(getattr(_serialport, "is_open", None)):
48 | _serialport.is_open
49 | else:
50 | _serialport.isOpen
51 | srv.logging.debug("%s already open", comName)
52 | except:
53 | #Open port for first use
54 | srv.logging.debug("Open %s with %d baud", comName, comBaudRate)
55 | _serialport = serial.serial_for_url(comName)
56 | _serialport.baudrate = comBaudRate
57 |
58 | _serialport.write(text.encode('utf-8'))
59 |
60 | except serial.SerialException as e:
61 | srv.logging.warning("Cannot write to com port `%s': %s" % (comName, e))
62 | return False
63 |
64 | return True
65 |
66 |
--------------------------------------------------------------------------------
/mqttwarn/services/slixmpp.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | # Based on xmpp plugin
5 | __originalauthor__ = 'Fabian Affolter '
6 | __author__ = 'Remi Vincent '
7 | __copyright__ = 'Copyright 2020 Remi Vincent'
8 | __license__ = 'Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)'
9 |
10 | import slixmpp
11 | import asyncio
12 |
13 | class send_msg_bot(slixmpp.ClientXMPP):
14 | def __init__(self, sender, password, recipient, message, loop):
15 | self.loop = loop
16 | asyncio.set_event_loop(loop)
17 | slixmpp.ClientXMPP.__init__(self, sender, password)
18 | self.recipient = recipient
19 | self.message = message
20 | self.add_event_handler("session_start", self.start)
21 |
22 | async def start(self, event):
23 | self.send_message(mto = self.recipient, mbody = self.message, mtype = 'chat')
24 | self.disconnect()
25 |
26 | def plugin(srv, item):
27 | """Send a message to XMPP recipient(s)."""
28 |
29 | srv.logging.debug("*** MODULE=%s: service=%s, target=%s", __file__, item.service, item.target)
30 |
31 | xmpp_addresses = item.addrs
32 | sender = item.config['sender']
33 | password = item.config['password']
34 | text = item.message
35 |
36 | if not xmpp_addresses:
37 | srv.logging.warn("Skipped sending XMPP notification to %s, "
38 | "no addresses configured" % (item.target))
39 | return False
40 |
41 | try:
42 | srv.logging.debug("Sending XMPP notification to %s, addresses: %s" % (item.target, xmpp_addresses))
43 | loop = asyncio.new_event_loop()
44 | for target in xmpp_addresses:
45 | xmpp = send_msg_bot(sender, password, target, text, loop)
46 | xmpp.connect()
47 | xmpp.process(forever=False)
48 | srv.logging.debug("Successfully sent message")
49 | except Exception as e:
50 | srv.logging.error("Error sending message to %s: %s" % (item.target, e))
51 | return False
52 |
53 | return True
54 |
--------------------------------------------------------------------------------
/mqttwarn/services/smtp.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | __author__ = 'Jan-Piet Mens '
5 | __copyright__ = 'Copyright 2014 Jan-Piet Mens'
6 | __license__ = 'Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)'
7 |
8 | from builtins import str
9 | import smtplib
10 | from email.mime.text import MIMEText
11 | from email.mime.multipart import MIMEMultipart
12 |
13 |
14 | def plugin(srv, item):
15 | """Send a message to SMTP recipient(s)."""
16 |
17 | srv.logging.debug("*** MODULE=%s: service=%s, target=%s", __file__, item.service, item.target)
18 |
19 | smtp_addresses = item.addrs
20 |
21 | server = item.config['server']
22 | sender = item.config['sender']
23 | starttls = item.config.get('starttls')
24 | username = item.config.get('username')
25 | password = item.config.get('password')
26 |
27 | if item.config.get("htmlmsg"):
28 | msg = MIMEMultipart('alternative')
29 | msg.attach(MIMEText(item.message, 'plain'))
30 | msg.attach(MIMEText(item.message, 'html'))
31 | else:
32 | msg = MIMEText(item.message, 'plain')
33 | msg['Subject'] = item.get('title', "%s notification" % (srv.SCRIPTNAME))
34 | msg['To'] = ", ".join(smtp_addresses)
35 | msg['From'] = sender
36 | msg['X-Mailer'] = srv.SCRIPTNAME
37 |
38 | if not smtp_addresses:
39 | srv.logging.warning("Skipped sending SMTP notification to %s, "
40 | "no addresses configured" % (item.target))
41 | return False
42 |
43 | try:
44 | srv.logging.debug("Sending SMTP notification to %s, addresses: %s" % (item.target, smtp_addresses))
45 | server = smtplib.SMTP(server)
46 | server.set_debuglevel(0)
47 | server.ehlo()
48 | if starttls:
49 | server.starttls()
50 | if username:
51 | server.login(str(username), str(password))
52 | server.sendmail(sender, smtp_addresses, msg.as_string())
53 | server.quit()
54 | srv.logging.debug("Successfully sent SMTP notification")
55 | except Exception as e:
56 | srv.logging.warning("Error sending notification to SMTP recipient %s, addresses: %s. "
57 | "Exception: %s" % (item.target, smtp_addresses, e))
58 | return False
59 |
60 | return True
61 |
--------------------------------------------------------------------------------
/mqttwarn/services/sqlite.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | __author__ = 'Jan-Piet Mens '
5 | __copyright__ = 'Copyright 2014 Jan-Piet Mens'
6 | __license__ = 'Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)'
7 |
8 | import sqlite3
9 |
10 |
11 | def plugin(srv, item):
12 | """ sqlite. Expects addrs to contain (path, tablename) """
13 |
14 | srv.logging.debug("*** MODULE=%s: service=%s, target=%s", __file__, item.service, item.target)
15 |
16 | path = item.addrs[0]
17 | table = item.addrs[1]
18 | try:
19 | conn = sqlite3.connect(path)
20 | except Exception as e:
21 | srv.logging.warn("Cannot connect to sqlite at %s : %s" % (path, e))
22 | return False
23 |
24 | c = conn.cursor()
25 | try:
26 | c.execute('CREATE TABLE IF NOT EXISTS %s (payload TEXT)' % table)
27 | except Exception as e:
28 | srv.logging.warn("Cannot create sqlite table in %s : %s" % (path, e))
29 | return False
30 |
31 | text = item.message
32 |
33 | try:
34 | c.execute('INSERT INTO %s VALUES (?)' % table, (text, ))
35 | conn.commit()
36 | c.close()
37 | except Exception as e:
38 | srv.logging.warn("Cannot INSERT INTO sqlite:%s : %s" % (table, e))
39 |
40 | return True
41 |
--------------------------------------------------------------------------------
/mqttwarn/services/sqlite_timestamp.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | __originalauthor__ = 'Jan-Piet Mens '
5 | __author__ = 'Kuthullu Himself '
6 | __copyright__ = 'Copyright 2016 Kuthullu Himself'
7 | __license__ = 'Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)'
8 |
9 | import sqlite3
10 |
11 |
12 | def plugin(srv, item):
13 | ''' sqlite. Expects addrs to contain (path, tablename) '''
14 |
15 | srv.logging.debug("*** MODULE=%s: service=%s, target=%s", __file__, item.service, item.target)
16 |
17 | path = item.addrs[0]
18 | table = item.addrs[1]
19 | try:
20 | conn = sqlite3.connect(path)
21 | except Exception as e:
22 | srv.logging.warn("Cannot connect to sqlite at %s : %s" % (path, e))
23 | return False
24 |
25 | c = conn.cursor()
26 | try:
27 | c.execute('CREATE TABLE IF NOT EXISTS %s (id INTEGER PRIMARY KEY AUTOINCREMENT, payload TEXT, timestamp DATETIME NOT NULL)' % table)
28 | except Exception as e:
29 | srv.logging.warn("Cannot create sqlite table in %s : %s" % (path, e))
30 | return False
31 |
32 | text = item.message
33 |
34 | try:
35 | c.execute('INSERT INTO %s VALUES (NULL, ?, datetime(\'now\'))' % table, (text, ))
36 | conn.commit()
37 | c.close()
38 | except Exception as e:
39 | srv.logging.warn("Cannot INSERT INTO sqlite:%s : %s" % (table, e))
40 |
41 | return True
42 |
--------------------------------------------------------------------------------
/mqttwarn/services/ssh.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from six import string_types
3 |
4 | __author__ = 'David Ventura'
5 | __copyright__ = 'Copyright 2016 David Ventura'
6 | __license__ = 'Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)'
7 |
8 | import os
9 | import json
10 | import paramiko
11 | from pipes import quote
12 |
13 |
14 | def credentials(host, user=None, pwd=None, port=22):
15 | c = {}
16 | if user is None and pwd is None:
17 | config = paramiko.SSHConfig()
18 | p = os.path.expanduser('~') + '/.ssh/config'
19 | config.parse(open(p))
20 | o = config.lookup(host)
21 |
22 | ident = o['identityfile']
23 | if type(ident) is list:
24 | ident = ident[0]
25 | if 'port' not in o:
26 | o['port'] = port
27 | c = {'hostname': o['hostname'], 'port': int(o['port']), 'username': o['user'], 'key_filename': ident}
28 | else:
29 | c = {'hostname': host, 'port': port, 'username': user, 'password': pwd}
30 |
31 | return c
32 |
33 |
34 | def plugin(srv, item):
35 | srv.logging.debug("*** MODULE=%s: service=%s, target=%s", __file__, item.service, item.target)
36 | host = item.config["host"]
37 | port = 22
38 | user = None
39 | pwd = None
40 |
41 | if 'port' in item.config:
42 | port = item.config["port"]
43 | if 'user' in item.config:
44 | user = item.config["user"]
45 | if 'pass' in item.config:
46 | pwd = item.config["pass"]
47 |
48 | command = item.addrs[0]
49 |
50 | args = json.loads(item.payload)["args"]
51 | if type(args) is list and len(args) == 1:
52 | args = args[0]
53 |
54 | if isinstance(args, list):
55 | args = tuple([quote(v) for v in args]) # escape the shell args
56 | elif isinstance(args, string_types):
57 | args = (quote(args),)
58 |
59 | command = command % args
60 |
61 | ssh = paramiko.SSHClient()
62 | ssh.load_system_host_keys()
63 | ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
64 |
65 | try:
66 | c = credentials(host, user=user, pwd=pwd, port=port)
67 | ssh.connect(**c)
68 | _, stdout, stderr = ssh.exec_command(command)
69 |
70 | except Exception as e:
71 | srv.logging.warning("Cannot run command '%s' on host '%s'" % (command, host))
72 | srv.logging.warning("%s" % e)
73 | return False
74 |
75 | return True
76 |
--------------------------------------------------------------------------------
/mqttwarn/services/syslog.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | __author__ = 'Fabian Affolter '
5 | __copyright__ = 'Copyright 2014 Fabian Affolter'
6 | __license__ = 'Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)'
7 |
8 | import syslog
9 |
10 |
11 | def plugin(srv, item):
12 | """Transfer a message to a syslog server."""
13 |
14 | srv.logging.debug("*** MODULE=%s: service=%s, target=%s",
15 | __file__, item.service, item.target)
16 |
17 | facilities = {
18 | 'kernel' : syslog.LOG_KERN,
19 | 'user' : syslog.LOG_USER,
20 | 'mail' : syslog.LOG_MAIL,
21 | 'daemon' : syslog.LOG_DAEMON,
22 | 'auth' : syslog.LOG_KERN,
23 | 'LPR' : syslog.LOG_LPR,
24 | 'news' : syslog.LOG_NEWS,
25 | 'uucp' : syslog.LOG_UUCP,
26 | 'cron' : syslog.LOG_CRON,
27 | 'syslog' : syslog.LOG_SYSLOG,
28 | 'local0' : syslog.LOG_LOCAL0,
29 | 'local1' : syslog.LOG_LOCAL1,
30 | 'local2' : syslog.LOG_LOCAL2,
31 | 'local3' : syslog.LOG_LOCAL3,
32 | 'local4' : syslog.LOG_LOCAL4,
33 | 'local5' : syslog.LOG_LOCAL5,
34 | 'local6' : syslog.LOG_LOCAL6,
35 | 'local7' : syslog.LOG_LOCAL7
36 | }
37 |
38 | options = {
39 | 'pid' : syslog.LOG_PID,
40 | 'cons' : syslog.LOG_CONS,
41 | 'ndelay' : syslog.LOG_NDELAY,
42 | 'nowait' : syslog.LOG_NOWAIT,
43 | 'perror' : syslog.LOG_PERROR
44 | }
45 |
46 | priorities = {
47 | 5 : syslog.LOG_EMERG,
48 | 4 : syslog.LOG_ALERT,
49 | 3 : syslog.LOG_CRIT,
50 | 2 : syslog.LOG_ERR,
51 | 1 : syslog.LOG_WARNING,
52 | 0 : syslog.LOG_NOTICE,
53 | -1 : syslog.LOG_INFO,
54 | -2 : syslog.LOG_DEBUG
55 | }
56 |
57 | title = item.get('title', srv.SCRIPTNAME)
58 | facility = facilities[item.addrs[0]]
59 | option = options[item.addrs[1]]
60 |
61 | priority = priorities[item.get('priority', -1)]
62 | message = item.message
63 |
64 | try:
65 | srv.logging.debug("Message is going to syslog facility %s..." \
66 | % ((item.target).upper()))
67 | syslog.openlog(title, option, facility)
68 | syslog.syslog(priority, message)
69 | srv.logging.debug("Successfully sent")
70 | syslog.closelog()
71 | except Exception as e:
72 | srv.logging.error("Error sending to syslog: %s" % e)
73 | syslog.closelog()
74 | return False
75 |
76 | return True
77 |
--------------------------------------------------------------------------------
/mqttwarn/services/tootpaste.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | from __future__ import print_function
4 | from builtins import str
5 |
6 | __author__ = 'Jan-Piet Mens '
7 | __copyright__ = 'Copyright 2017 Jan-Piet Mens'
8 | __license__ = 'Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)'
9 |
10 | import os
11 | import sys
12 | from mastodon import Mastodon
13 |
14 |
15 | def plugin(srv, item):
16 | _DEFAULT_URL = 'https://mastodon.social'
17 |
18 | srv.logging.debug("*** MODULE=%s: service=%s, target=%s", __file__, item.service, item.target)
19 |
20 | # item.config is brought in from the configuration file
21 | config = item.config
22 |
23 | clientcreds, usercreds, base_url = item.addrs
24 |
25 | text = item.message
26 |
27 | try:
28 | mastodon = Mastodon(
29 | client_id=clientcreds,
30 | access_token=usercreds,
31 | api_base_url=base_url
32 | )
33 |
34 | mastodon.toot(text)
35 | except Exception as e:
36 | srv.logging.warning("Cannot post to Mastodon: %s" % (str(e)))
37 | return False
38 |
39 | return True
40 |
41 |
42 | if __name__ == '__main__':
43 | from mastodon import Mastodon
44 |
45 | try:
46 | base_url, email, password, clientname, clientcred, usercred = sys.argv[1:]
47 | except:
48 | print("Usage: %s base_url email password clientname clientcredsfile usercredsfile" % sys.argv[0])
49 | sys.exit(2)
50 |
51 | if not os.path.isfile(clientcred):
52 | Mastodon.create_app(
53 | clientname,
54 | to_file=clientcred,
55 | scopes=['read', 'write'],
56 | api_base_url=base_url)
57 |
58 | if not os.path.isfile(usercred):
59 | mastodon_api = Mastodon(
60 | client_id=clientcred,
61 | api_base_url=base_url)
62 |
63 | mastodon_api.log_in(
64 | email,
65 | password,
66 | to_file=usercred,
67 | scopes=['read', 'write'],
68 | )
69 |
--------------------------------------------------------------------------------
/mqttwarn/services/twilio.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | __author__ = 'Jan-Piet Mens '
5 | __copyright__ = 'Copyright 2014 Jan-Piet Mens'
6 | __license__ = 'Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)'
7 |
8 | from twilio.rest import TwilioRestClient
9 |
10 |
11 | def plugin(srv, item):
12 | """ expects (accountSID, authToken, from, to) in addrs"""
13 |
14 | srv.logging.debug("*** MODULE=%s: service=%s, target=%s", __file__, item.service, item.target)
15 |
16 | try:
17 | account_sid, auth_token, from_nr, to_nr = item.addrs
18 | except:
19 | srv.logging.warn("Twilio target is incorrectly configured")
20 | return False
21 |
22 | text = item.message
23 |
24 | try:
25 | client = TwilioRestClient(account_sid, auth_token)
26 | message = client.messages.create(
27 | body=text,
28 | to=to_nr,
29 | from_=from_nr)
30 | srv.logging.debug("Twilio returns %s" % (message.sid))
31 | except Exception as e:
32 | srv.logging.warn("Twilio failed: %s" % e)
33 | return False
34 |
35 | return True
36 |
--------------------------------------------------------------------------------
/mqttwarn/services/twitter.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | __author__ = 'Jan-Piet Mens '
5 | __copyright__ = 'Copyright 2014 Jan-Piet Mens'
6 | __license__ = 'Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)'
7 |
8 | import twitter
9 |
10 |
11 | def plugin(srv, item):
12 | srv.logging.debug("*** MODULE=%s: service=%s, target=%s", __file__, item.service, item.target)
13 |
14 | twitter_keys = item.addrs
15 |
16 | twapi = twitter.Api(
17 | consumer_key=twitter_keys[0],
18 | consumer_secret=twitter_keys[1],
19 | access_token_key=twitter_keys[2],
20 | access_token_secret=twitter_keys[3]
21 | )
22 |
23 | text = item.message[0:138]
24 | try:
25 | srv.logging.debug("Sending tweet to %s..." % (item.target))
26 | res = twapi.PostUpdate(text, trim_user=False)
27 | srv.logging.debug("Successfully sent tweet")
28 | except twitter.TwitterError as e:
29 | srv.logging.error("TwitterError: %s" % e)
30 | return False
31 | except Exception as e:
32 | srv.logging.error("Error sending tweet to %s: %s" % (item.target, e))
33 | return False
34 |
35 | return True
36 |
--------------------------------------------------------------------------------
/mqttwarn/services/websocket.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | __author__ = 'Giovanni Angoli '
5 | __copyright__ = 'Copyright 2018 Giovanni Angoli'
6 | __license__ = 'Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)'
7 |
8 | # This is basically the file.py service but designed for websockets, not more than s/file/websocket/g.
9 |
10 | import websocket
11 |
12 |
13 | def plugin(srv, item):
14 |
15 | srv.logging.debug("*** MODULE=%s: service=%s, target=%s", __file__, item.service, item.target)
16 |
17 |
18 | # item.config is brought in from the configuration file
19 | config = item.config
20 |
21 | # addrs is a list[] associated with a particular target.
22 | # While it may contain more than one item (e.g. pushover)
23 | # the `websocket' service carries one only, i.e. a ws:// or wss:// uri
24 | uri = item.addrs[0].format(**item.data).encode('utf-8')
25 |
26 | # If the incoming payload has been transformed, use that,
27 | # else the original payload
28 | text = item.message
29 |
30 | try:
31 | ws = websocket.WebSocket()
32 | ws.connect(uri)
33 | ws.send(text)
34 | ws.close()
35 | except Exception as e:
36 | srv.logging.warning("Cannot write to websocket `%s': %s" % (uri, e))
37 | return False
38 |
39 | return True
40 |
--------------------------------------------------------------------------------
/mqttwarn/services/xbmc.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | __author__ = 'Ben Jones '
5 | __copyright__ = 'Copyright 2014 Ben Jones'
6 | __license__ = 'Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)'
7 |
8 | from future import standard_library
9 | standard_library.install_aliases()
10 |
11 | import urllib.request, urllib.parse, urllib.error
12 | import base64
13 | try:
14 | import simplejson as json
15 | except ImportError:
16 | import json # type: ignore[no-redef]
17 |
18 |
19 | def plugin(srv, item):
20 |
21 | srv.logging.debug("*** MODULE=%s: service=%s, target=%s", __file__, item.service, item.target)
22 |
23 | xbmchost = item.addrs[0]
24 | xbmcusername = None
25 | xbmcpassword = None
26 |
27 | if len(item.addrs) == 3:
28 | xbmcusername = item.addrs[1]
29 | xbmcpassword = item.addrs[2]
30 |
31 | title = item.title
32 | message = item.message
33 | image = item.image
34 |
35 | jsonparams = {
36 | "jsonrpc" : "2.0",
37 | "method" : "GUI.ShowNotification",
38 | "id" : 1,
39 | "params" : {
40 | "title" : title,
41 | "message" : message,
42 | "image" : image,
43 | "displaytime" : 10000
44 | }
45 | }
46 | jsoncommand = json.dumps(jsonparams).encode("utf-8")
47 |
48 | url = 'http://%s/jsonrpc' % (xbmchost)
49 | try:
50 | srv.logging.debug("Sending XBMC notification to %s [%s]..." % (item.target, xbmchost))
51 | req = urllib.request.Request(url, jsoncommand)
52 | req.add_header("Content-type", "application/json")
53 | if xbmcpassword is not None:
54 | credentials = '%s:%s' % (xbmcusername, xbmcpassword)
55 | basicauth_token = base64.b64encode(credentials.encode('utf-8')).decode()
56 | authheader = "Basic %s" % basicauth_token
57 | req.add_header("Authorization", authheader)
58 | response = urllib.request.urlopen(req, timeout = 2)
59 | srv.logging.debug("Successfully sent XBMC notification")
60 | except urllib.error.URLError as e:
61 | srv.logging.error("URLError: %s" % e)
62 | return False
63 | except Exception as e:
64 | srv.logging.error("Error sending XBMC notification to %s [%s]: %s" % (item.target, xbmchost, e))
65 | return False
66 |
67 | return True
68 |
--------------------------------------------------------------------------------
/mqttwarn/services/xmpp.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | __author__ = 'Fabian Affolter '
5 | __copyright__ = 'Copyright 2014 Fabian Affolter'
6 | __license__ = 'Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)'
7 |
8 | import xmpp
9 |
10 |
11 | def plugin(srv, item):
12 | """Send a message to XMPP recipient(s)."""
13 |
14 | srv.logging.debug("*** MODULE=%s: service=%s, target=%s", __file__, item.service, item.target)
15 |
16 | xmpp_addresses = item.addrs
17 | sender = item.config['sender']
18 | password = item.config['password']
19 | text = item.message
20 |
21 | if not xmpp_addresses:
22 | srv.logging.warn("Skipped sending XMPP notification to %s, "
23 | "no addresses configured" % (item.target))
24 | return False
25 |
26 | try:
27 | srv.logging.debug("Sending XMPP notification to %s, addresses: %s" % (item.target, xmpp_addresses))
28 | for target in xmpp_addresses:
29 | jid = xmpp.protocol.JID(sender)
30 | connection = xmpp.Client(jid.getDomain(),debug=[])
31 | connection.connect()
32 | connection.auth(jid.getNode(), password, resource=jid.getResource())
33 | connection.send(xmpp.protocol.Message(target, text))
34 | srv.logging.debug("Successfully sent message")
35 | except Exception as e:
36 | srv.logging.error("Error sending message to %s: %s" % (item.target, e))
37 | return False
38 |
39 | return True
40 |
41 |
42 | def xmpppy_monkeypatch_ssl():
43 | """
44 | Mitigate "AttributeError: '_ssl._SSLSocket' object has no attribute 'issuer'"
45 |
46 | Monkey-patched _startSSL method from
47 | https://raw.githubusercontent.com/freebsd/freebsd-ports/master/net-im/py-xmpppy/files/patch-xmpp-transports.py
48 | """
49 | import ssl
50 |
51 | def _startSSL(self):
52 | """ Immidiatedly switch socket to TLS mode. Used internally."""
53 | """ Here we should switch pending_data to hint mode."""
54 | tcpsock=self._owner.Connection
55 | tcpsock._sslObj = ssl.wrap_socket(tcpsock._sock, None, None)
56 | tcpsock._sslIssuer = tcpsock._sslObj.getpeercert().get('issuer')
57 | tcpsock._sslServer = tcpsock._sslObj.getpeercert().get('server')
58 | tcpsock._recv = tcpsock._sslObj.read
59 | tcpsock._send = tcpsock._sslObj.write
60 |
61 | tcpsock._seen_data=1
62 | self._tcpsock=tcpsock
63 | tcpsock.pending_data=self.pending_data
64 | tcpsock._sock.setblocking(0)
65 |
66 | self.starttls='success'
67 |
68 | from xmpp.transports import TLS
69 | TLS._startSSL = _startSSL
70 |
--------------------------------------------------------------------------------
/mqttwarn/testing/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mqtt-tools/mqttwarn/d9d312df66b428bf36c053ebaa88e6de18f8aa37/mqttwarn/testing/__init__.py
--------------------------------------------------------------------------------
/mqttwarn/testing/fixtures.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | import pytest
4 |
5 | from mqttwarn.model import Service
6 |
7 |
8 | @pytest.fixture
9 | def mqttwarn_service():
10 | """
11 | A service instance for propagating to the plugin.
12 | """
13 | logger = logging.getLogger(__name__)
14 | # FIXME: Should propagate `mqttwarn.core.globals()` to `mwcore`.
15 | return Service(mqttc=None, logger=logger, mwcore={}, program="mqttwarn-testdrive")
16 |
--------------------------------------------------------------------------------
/mqttwarn/vendor/ZabbixSender.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | from __future__ import print_function
4 | from builtins import str
5 | import socket
6 | import struct
7 |
8 | # Single file, imported from https://github.com/BlueSkyDetector/code-snippet/tree/master/ZabbixSender
9 | # Lincense: DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
10 | # Version 2, December 2004
11 | # Copyright (C) 2010 Takanori Suzuki
12 |
13 | # JPM: s/simplejson/json/g
14 |
15 | try:
16 | import json
17 | except ImportError:
18 | import simplejson as json # type: ignore[no-redef]
19 |
20 |
21 | class ZabbixSender:
22 |
23 | zbx_header = b'ZBXD'
24 | zbx_version = 1
25 | zbx_sender_data = {'request': 'sender data', 'data': []}
26 | send_data = ''
27 |
28 | def __init__(self, server_host, server_port = 10051):
29 | self.server_ip = socket.gethostbyname(server_host)
30 | self.server_port = server_port
31 |
32 | def AddData(self, host, key, value, clock = None):
33 | add_data = {'host': host, 'key': key, 'value': value}
34 | if clock != None:
35 | add_data['clock'] = clock
36 | self.zbx_sender_data['data'].append(add_data)
37 | return self.zbx_sender_data
38 |
39 | def ClearData(self):
40 | self.zbx_sender_data['data'] = []
41 | return self.zbx_sender_data
42 |
43 | def __MakeSendData(self):
44 | zbx_sender_json = json.dumps(self.zbx_sender_data, separators=(',', ':'), ensure_ascii=False).encode('utf-8')
45 | json_byte = len(zbx_sender_json)
46 | self.send_data = struct.pack("<4sBq" + str(json_byte) + "s", self.zbx_header, self.zbx_version, json_byte, zbx_sender_json)
47 |
48 | def Send(self):
49 | self.__MakeSendData()
50 | so = socket.socket()
51 | so.connect((self.server_ip, self.server_port))
52 | wobj = so.makefile('wb')
53 | wobj.write(self.send_data)
54 | wobj.close()
55 | robj = so.makefile('rb')
56 | recv_data = robj.read()
57 | robj.close()
58 | so.close()
59 | tmp_data = struct.unpack("<4sBq" + str(len(recv_data) - struct.calcsize("<4sBq")) + "s", recv_data)
60 | recv_json = json.loads(tmp_data[3])
61 | #JPM return recv_data
62 | return recv_json
63 |
64 |
65 | if __name__ == '__main__':
66 | sender = ZabbixSender('127.0.0.1')
67 | for num in range(0, 2):
68 | sender.AddData('HostA', 'AppX_Logger', 'sent data 第' + str(num))
69 | res = sender.Send()
70 | print(sender.send_data)
71 | print(res)
72 |
--------------------------------------------------------------------------------
/mqttwarn/vendor/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mqtt-tools/mqttwarn/d9d312df66b428bf36c053ebaa88e6de18f8aa37/mqttwarn/vendor/__init__.py
--------------------------------------------------------------------------------
/requirements-release.txt:
--------------------------------------------------------------------------------
1 | keyring
2 | twine
3 |
--------------------------------------------------------------------------------
/templates/demo.j2:
--------------------------------------------------------------------------------
1 | {#
2 | this is a comment
3 | in Jinja2
4 | See http://jinja.pocoo.org/docs/templates/ for information
5 | on Jinja2 templates.
6 | #}
7 | {% set upname = name | upper %}
8 | {% set width = 60 %}
9 | {% for n in range(0, width) %}-{% endfor %}
10 |
11 | Name.................: {{ upname }}
12 | Number...............: {{ number }}
13 | Timestamp............: {{ _dthhmm }}
14 | Original payload.....: {{ payload }}
15 |
--------------------------------------------------------------------------------
/templates/hiveeyes-alert.j2:
--------------------------------------------------------------------------------
1 | {#
2 | Hiveeyes alert template
3 | #}
4 | Alarm from beehive "{{node}}".
5 | {{description}}
6 |
7 | {% print '-' * 42 %}
8 |
9 | Network..............: {{ network }}
10 | Gateway..............: {{ gateway }}
11 | Node.................: {{ node }}
12 | Timestamp UTC........: {{ _dtiso }}
13 | Current data.........: {{ pformat(history['current'], width=120) }}
14 | Previous data........: {{ pformat(history['previous'], width=120) }}
15 | {% print '-' * 42 %}
16 |
17 | Current fragment.....: {{ pformat(fragments['current'], width=120) }}
18 | Previous fragment....: {{ pformat(fragments['previous'], width=120) }}
19 | Original payload.....: {{ payload }}
20 | {% print '-' * 42 %}
21 |
22 | Grafana dashboard....: https://swarm.hiveeyes.org/grafana/dashboard/db/{{ dashboard }}
23 |
--------------------------------------------------------------------------------
/templates/test.jinja:
--------------------------------------------------------------------------------
1 | {# This is a comment in Jinja2. #}
2 | {% set upname = name | upper %}
3 | Name: {{ upname }}
4 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # (c) 2018 The mqttwarn developers
3 |
4 | # Configuration- and function files used by the test harness
5 | configfile_full = "tests/etc/full.ini"
6 | configfile_service_loading = "tests/etc/service-loading.ini"
7 | configfile_no_functions = "tests/etc/no-functions.ini"
8 | configfile_empty_functions = "tests/etc/empty-functions.ini"
9 | configfile_logging_levels = "tests/etc/logging-levels.ini"
10 | configfile_better_addresses = "tests/etc/better-addresses.ini"
11 | configfile_with_variables = "tests/etc/with-variables.ini"
12 | funcfile_good = "tests/etc/functions_good.py"
13 | funcfile_bad = "tests/etc/functions_bad.py"
14 |
--------------------------------------------------------------------------------
/tests/acme/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mqtt-tools/mqttwarn/d9d312df66b428bf36c053ebaa88e6de18f8aa37/tests/acme/__init__.py
--------------------------------------------------------------------------------
/tests/acme/foobar.py:
--------------------------------------------------------------------------------
1 | def plugin(srv, item):
2 | srv.logging.debug("*** MODULE=%s: service=%s, target=%s", __file__, item.service, item.target)
3 | srv.logging.info("Plugin invoked")
4 |
--------------------------------------------------------------------------------
/tests/conftest.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # (c) 2018-2023 The mqttwarn developers
3 | import importlib
4 | import os
5 | import pathlib
6 | import shutil
7 | import sys
8 | import typing as t
9 | from tempfile import NamedTemporaryFile
10 |
11 | import pytest
12 |
13 | # Needed to make Apprise not be mocked too much.
14 | from mqttwarn.services.apprise_util import get_all_template_argument_names # noqa:F401
15 |
16 | # Import custom fixtures.
17 | from mqttwarn.testing.fixtures import mqttwarn_service as srv # noqa:F401
18 | from tests.fixtures.ntfy import ntfy_service # noqa:F401
19 |
20 |
21 | @pytest.fixture
22 | def fake_filesystem(fs): # pylint:disable=invalid-name
23 | try:
24 | fs.create_dir("/tmp")
25 | except Exception:
26 | pass
27 | yield fs
28 |
29 |
30 | @pytest.fixture
31 | def without_jinja():
32 |
33 | # Emulate removal of `jinja2` package.
34 | # https://stackoverflow.com/a/65163627
35 | backup = sys.modules["jinja2"]
36 | sys.modules["jinja2"] = None
37 | importlib.reload(sys.modules["mqttwarn.core"])
38 |
39 | yield
40 |
41 | # Restore `jinja2` package.
42 | sys.modules["jinja2"] = backup
43 | importlib.reload(sys.modules["mqttwarn.core"])
44 |
45 |
46 | @pytest.fixture
47 | def without_ssl():
48 |
49 | # Emulate removal of `ssl` package.
50 | # https://stackoverflow.com/a/65163627
51 | backup = sys.modules["ssl"]
52 | sys.modules["ssl"] = None
53 | importlib.reload(sys.modules["mqttwarn.configuration"])
54 |
55 | yield
56 |
57 | # Restore `jinja2` package.
58 | sys.modules["ssl"] = backup
59 | importlib.reload(sys.modules["mqttwarn.configuration"])
60 |
61 |
62 | @pytest.fixture
63 | def mqttwarn_bin():
64 | """
65 | Find `mqttwarn` executable, located within the inline virtualenv.
66 | """
67 |
68 | path_candidates = [None, ".venv/bin", r".venv\Scripts"]
69 | for path_candidate in path_candidates:
70 | mqttwarn_bin = shutil.which("mqttwarn", path=path_candidate)
71 | if mqttwarn_bin is not None:
72 | return mqttwarn_bin
73 |
74 | raise FileNotFoundError(f"Unable to discover 'mqttwarn' executable within {path_candidates}")
75 |
76 |
77 | @pytest.fixture()
78 | def tmp_ini(tmp_path) -> pathlib.Path:
79 | """
80 | Provide temporary INI files to test cases.
81 | """
82 | filepath = tmp_path.joinpath("testdrive.ini")
83 | return filepath
84 |
85 |
86 | @pytest.fixture
87 | def attachment_dummy() -> t.Generator[t.IO[bytes], None, None]:
88 | """
89 | Provide a temporary file to the test cases to be used as an attachment with defined content.
90 | """
91 | tmp = NamedTemporaryFile(suffix=".txt", delete=False)
92 | tmp.write(b"foo")
93 | tmp.close()
94 | yield tmp
95 | os.unlink(tmp.name)
96 |
--------------------------------------------------------------------------------
/tests/etc/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mqtt-tools/mqttwarn/d9d312df66b428bf36c053ebaa88e6de18f8aa37/tests/etc/__init__.py
--------------------------------------------------------------------------------
/tests/etc/better-addresses.ini:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # (c) 2023 The mqttwarn developers
3 | #
4 | # mqttwarn configuration file for testing "improved
5 | # addresses configuration", with named parameters.
6 | #
7 |
8 | ; -------
9 | ; General
10 | ; -------
11 |
12 | [defaults]
13 |
14 |
15 | ; ----
16 | ; MQTT
17 | ; ----
18 |
19 | hostname = 'localhost'
20 | port = 1883
21 | username = None
22 | password = None
23 | clientid = 'mqttwarn-testdrive'
24 | lwt = 'clients/mqttwarn-testdrive'
25 | skipretained = False
26 | cleansession = False
27 |
28 | # MQTTv31 = 3 (default)
29 | # MQTTv311 = 4
30 | protocol = 3
31 |
32 |
33 | ; -------
34 | ; Logging
35 | ; -------
36 |
37 | ; Send log output to STDERR
38 | logfile = 'stream://sys.stderr'
39 |
40 | ; Send log output to file
41 | ;logfile = 'mqttwarn.log'
42 |
43 | ; one of: CRITICAL, DEBUG, ERROR, INFO, WARN
44 | loglevel = DEBUG
45 |
46 |
47 | ; --------
48 | ; Services
49 | ; --------
50 |
51 | ; name the service providers you will be using.
52 | launch = apprise
53 |
54 | [config:apprise]
55 | ; Dispatch message to multiple Apprise plugins.
56 | module = 'apprise_multi'
57 | targets = {
58 | 'demo-http' : [ { 'baseuri': 'json://localhost:1234/mqtthook' }, { 'baseuri': 'json://daq.example.org:5555/foobar' } ],
59 | 'demo-discord' : [ { 'baseuri': 'discord://4174216298/JHMHI8qBe7bk2ZwO5U711o3dV_js' } ],
60 | 'demo-mailto' : [ {
61 | 'baseuri': 'mailtos://smtp_username:smtp_password@mail.example.org',
62 | 'recipients': ['foo@example.org', 'bar@example.org'],
63 | 'sender': 'monitoring@example.org',
64 | 'sender_name': 'Example Monitoring',
65 | } ],
66 | }
67 |
68 |
69 | [config:pushsafer]
70 | ; https://www.pushsafer.com/en/pushapi
71 | ; https://www.pushsafer.com/en/pushapi_ext
72 | targets = {
73 | 'basic': { 'private_key': '3SAz1a2iTYsh19eXIMiO' },
74 | 'nagios': {
75 | 'private_key': '3SAz1a2iTYsh19eXIMiO',
76 | 'device': '52|65|78',
77 | 'icon': 64,
78 | 'sound': 2,
79 | 'vibration': 1,
80 | 'url': 'http://example.org',
81 | 'url_title': 'Example Org',
82 | 'time_to_live': 60,
83 | 'priority': 2,
84 | 'retry': 60,
85 | 'expire': 600,
86 | 'answer': 1,
87 | },
88 | 'tracking': {
89 | 'private_key': '3SAz1a2iTYsh19eXIMiO',
90 | 'device': 'gs23',
91 | 'icon': 18,
92 | },
93 | 'bogus': { 'foo': 'bar' },
94 | }
95 |
96 |
97 |
98 | ; -------
99 | ; Targets
100 | ; -------
101 |
102 | [apprise-test]
103 | topic = apprise/#
104 | targets = apprise:demo-http, apprise:demo-discord, apprise:demo-mailto
105 | format = Alarm from {device}: {payload}
106 | title = Alarm from {device}
107 |
--------------------------------------------------------------------------------
/tests/etc/empty-functions.ini:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # (c) 2014-2022 The mqttwarn developers
3 | #
4 | # mqttwarn configuration file for testing with empty user defined `functions` setting.
5 | #
6 |
7 | ; -------
8 | ; General
9 | ; -------
10 |
11 | [defaults]
12 |
13 |
14 | ; This is an *empty* `functions` setting.
15 | functions =
16 |
17 | ; name the service providers you will be using.
18 | launch = log
19 |
20 |
21 | ; --------
22 | ; Services
23 | ; --------
24 |
25 | [config:log]
26 | targets = {
27 | 'debug' : [ 'debug' ],
28 | 'info' : [ 'info' ],
29 | 'warn' : [ 'warn' ],
30 | 'crit' : [ 'crit' ],
31 | 'error' : [ 'error' ]
32 | }
33 |
34 |
35 | ; -------
36 | ; Targets
37 | ; -------
38 |
39 | [test/log-1]
40 | ; echo '{"name": "temperature", "value": 42.42}' | mosquitto_pub -h localhost -t test/log-1 -l
41 | targets = log:info
42 | format = {name}: {value}
43 |
--------------------------------------------------------------------------------
/tests/etc/functions_bad.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # (c) 2020 The mqttwarn developers
3 |
4 | def foobar():
5 | """
6 | This function has an intentional indentation error, for
7 | validating mqttwarn runtime behavior with bogus Python code.
8 | """
9 | foo
10 | return True
11 |
--------------------------------------------------------------------------------
/tests/etc/functions_good.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # (c) 2018-2022 The mqttwarn developers
3 | import logging
4 |
5 | logger = logging.getLogger(__name__)
6 |
7 |
8 | def foobar():
9 | return True
10 |
11 |
12 | def cronfunc(srv):
13 | logger.info("`cronfunc` called")
14 | return True
15 |
16 |
17 | def xform_func(data):
18 | data["xform-key"] = "xform-value"
19 | return data
20 |
21 |
22 | def datamap_dummy_v1(topic):
23 | return {"datamap-key": "datamap-value"}
24 |
25 |
26 | def datamap_dummy_v2(topic, srv):
27 | return {"datamap-key": "datamap-value"}
28 |
29 |
30 | def alldata_dummy(topic, data, srv):
31 | return {"alldata-key": "alldata-value"}
32 |
33 |
34 | def filter_dummy_v1(topic, message):
35 | do_skip = "reject" in message
36 | return do_skip
37 |
38 |
39 | def filter_dummy_v2(topic, message, section, srv):
40 | do_skip = "reject" in message
41 | return do_skip
42 |
43 |
44 | def get_targets_valid(srv, topic, data):
45 | return ["log:info"]
46 |
47 |
48 | def get_targets_invalid(srv, topic, data):
49 | return ["log:invalid"]
50 |
51 |
52 | def get_targets_broken(srv, topic, data):
53 | return "broken"
54 |
55 |
56 | def get_targets_error(srv, topic, data):
57 | raise ValueError("Something failed")
58 |
--------------------------------------------------------------------------------
/tests/etc/logging-levels.ini:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # (c) 2014-2022 The mqttwarn developers
3 | #
4 | # mqttwarn configuration file for testing log level tuning
5 | #
6 |
7 | ; -------
8 | ; General
9 | ; -------
10 |
11 | [defaults]
12 |
13 | ; -------
14 | ; Logging
15 | ; -------
16 |
17 | filteredmessagesloglevel = DEBUG
18 |
19 | ; --------
20 | ; Services
21 | ; --------
22 |
23 | ; path to file containing self-defined functions like `format` or `alldata`
24 | ; generate with "mqttwarn make-udf"
25 | functions = 'tests/etc/functions_good.py'
26 |
27 | ; name the service providers you will be using.
28 | launch = log, file, tests.acme.foobar, tests/acme/foobar.py
29 |
30 | [config:log]
31 | targets = {
32 | 'debug' : [ 'debug' ],
33 | 'info' : [ 'info' ],
34 | 'warn' : [ 'warn' ],
35 | 'crit' : [ 'crit' ],
36 | 'error' : [ 'error' ],
37 | 'invalid': [ 'invalid' ],
38 | 'broken': 'broken'
39 | }
40 |
41 | [config:file]
42 | append_newline = True
43 | targets = {
44 | 'test-1' : ['$TMPDIR/mqttwarn-test.01'],
45 | 'test-2' : ['$TMPDIR/mqttwarn-test.02'],
46 | }
47 |
48 | [config:tests.acme.foobar]
49 | targets = {
50 | 'default' : [ 'default' ],
51 | }
52 |
53 | [config:tests/acme/foobar.py]
54 | targets = {
55 | 'default' : [ 'default' ],
56 | }
57 |
58 | ; -------
59 | ; Targets
60 | ; -------
61 |
62 | [test/filter-1]
63 | targets = log:info
64 | filter = filter_dummy_v1()
65 |
--------------------------------------------------------------------------------
/tests/etc/no-functions.ini:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # (c) 2014-2021 The mqttwarn developers
3 | #
4 | # mqttwarn configuration file for testing without user defined `functions` setting.
5 | #
6 |
7 | ; -------
8 | ; General
9 | ; -------
10 |
11 | [defaults]
12 |
13 | ; This is *without* a `functions` setting.
14 | ;functions = 'DO NOT SET'
15 |
16 | ; name the service providers you will be using.
17 | launch = log, file
18 |
19 |
20 | ; --------
21 | ; Services
22 | ; --------
23 |
24 | [config:log]
25 | targets = {
26 | 'debug' : [ 'debug' ],
27 | 'info' : [ 'info' ],
28 | 'warn' : [ 'warn' ],
29 | 'crit' : [ 'crit' ],
30 | 'error' : [ 'error' ]
31 | }
32 |
33 | [config:file]
34 | targets = {
35 | 'spool-binary': ['/tmp/mqttwarn-test-spool.jpg'],
36 | }
37 |
38 | # Pass-through payload content 1:1.
39 | append_newline = False
40 | decode_utf8 = False
41 | overwrite = True
42 |
43 |
44 | ; -------
45 | ; Targets
46 | ; -------
47 |
48 | [test/log-1]
49 | ; echo '{"name": "temperature", "value": 42.42}' | mosquitto_pub -h localhost -t test/log-1 -l
50 | targets = log:info
51 | format = {name}: {value}
52 |
53 | [test/file-1]
54 | ; wget -O goat.png https://user-images.githubusercontent.com/453543/231550862-5a64ac7c-bdfa-4509-86b8-b1a770899647.png
55 | ; mosquitto_pub -f goat.png -t 'test/file-1'
56 | targets = file:spool-binary
57 |
--------------------------------------------------------------------------------
/tests/etc/password.txt:
--------------------------------------------------------------------------------
1 | secret-password
--------------------------------------------------------------------------------
/tests/etc/service-loading.ini:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # (c) 2014-2021 The mqttwarn developers
3 | #
4 | # mqttwarn configuration file for testing function and module loading with relative paths.
5 | #
6 |
7 | ; -------
8 | ; General
9 | ; -------
10 |
11 | [defaults]
12 |
13 |
14 | ; path to file containing self-defined functions like `format` or `alldata`
15 | ; generate with "mqttwarn make-udf"
16 | functions = 'functions_good.py'
17 |
18 | ; name the service providers you will be using.
19 | launch = tests.acme.foobar, ../acme/foobar.py
20 |
21 |
22 | ; --------
23 | ; Services
24 | ; --------
25 |
26 | [config:tests.acme.foobar]
27 | targets = {
28 | 'default' : [ 'default' ],
29 | }
30 |
31 | [config:../acme/foobar.py]
32 | targets = {
33 | 'default' : [ 'default' ],
34 | }
35 |
36 |
37 | ; -------
38 | ; Targets
39 | ; -------
40 |
41 | [test/plugin-module]
42 | ; echo '{"name": "temperature", "value": 42.42}' | mosquitto_pub -h localhost -t test/plugin-module -l
43 | targets = tests.acme.foobar:default
44 | format = {name}: {value}
45 |
46 | [test/plugin-file]
47 | ; echo '{"name": "temperature", "value": 42.42}' | mosquitto_pub -h localhost -t test/plugin-file -l
48 | targets = ../acme/foobar.py:default
49 | format = {name}: {value}
50 |
--------------------------------------------------------------------------------
/tests/etc/with-variables.ini:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # (c) 2023 The mqttwarn developers
3 | #
4 | # mqttwarn configuration file for testing variable expansion.
5 | #
6 |
7 | ; -------
8 | ; General
9 | ; -------
10 |
11 | [defaults]
12 | hostname = $ENV:HOSTNAME
13 | port = $ENV:PORT
14 | username = ${ENV:USERNAME}
15 | password = ${FILE:./password.txt}
16 |
17 | ; name the service providers you will be using.
18 | launch = file
19 |
20 |
21 | ; --------
22 | ; Services
23 | ; --------
24 |
25 | [config:file]
26 | targets = {
27 | 'mylog' : [ '$ENV:LOG_FILE' ],
28 | }
29 |
--------------------------------------------------------------------------------
/tests/fixtures/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mqtt-tools/mqttwarn/d9d312df66b428bf36c053ebaa88e6de18f8aa37/tests/fixtures/__init__.py
--------------------------------------------------------------------------------
/tests/fixtures/ntfy.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Copyright (c) 2023 Andreas Motl
3 | #
4 | # Use of this source code is governed by an MIT-style
5 | # license that can be found in the LICENSE file or at
6 | # https://opensource.org/licenses/MIT.
7 | """
8 | Provide the `ntfy`_ API service as a session-scoped fixture to your test
9 | harness.
10 |
11 | Source: https://docs.ntfy.sh/install/#docker
12 |
13 | .. _ntfy: https://ntfy.sh/
14 | """
15 | import docker
16 | import pytest
17 | from pytest_docker_fixtures import images
18 | from pytest_docker_fixtures.containers._base import BaseImage
19 | from pytest_mqtt.util import probe_tcp_connect
20 |
21 | images.settings["ntfy"] = {
22 | "image": "binwiederhier/ntfy",
23 | "version": "latest",
24 | "options": {
25 | "command": """
26 | serve
27 | --base-url="http://localhost:5555"
28 | --attachment-cache-dir="/tmp/ntfy-attachments"
29 | """,
30 | "publish_all_ports": False,
31 | "ports": {"80/tcp": "5555"},
32 | },
33 | }
34 |
35 |
36 | class Ntfy(BaseImage):
37 |
38 | name = "ntfy"
39 |
40 | def check(self):
41 | # TODO: Add real implementation.
42 | return True
43 |
44 | def pull_image(self):
45 | """
46 | Image needs to be pulled explicitly.
47 | Workaround against `404 Client Error: Not Found for url: http+docker://localhost/v1.23/containers/create`.
48 |
49 | - https://github.com/mqtt-tools/mqttwarn/pull/589#issuecomment-1249680740
50 | - https://github.com/docker/docker-py/issues/2101
51 | """
52 | docker_client = docker.from_env(version=self.docker_version)
53 | image_name = self.image
54 | docker_client.images.pull(image_name)
55 |
56 | def run(self):
57 | self.pull_image()
58 | super(Ntfy, self).run()
59 |
60 |
61 | ntfy_image = Ntfy()
62 |
63 |
64 | def is_ntfy_running() -> bool:
65 | return probe_tcp_connect("localhost", 5555)
66 |
67 |
68 | @pytest.fixture(scope="session")
69 | def ntfy_service():
70 |
71 | # Gracefully skip spinning up the Docker container if ntfy is already running.
72 | if is_ntfy_running():
73 | yield "localhost", 5555
74 | return
75 |
76 | ntfy_image.run()
77 | yield "localhost", 5555
78 | ntfy_image.stop()
79 |
--------------------------------------------------------------------------------
/tests/services/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mqtt-tools/mqttwarn/d9d312df66b428bf36c053ebaa88e6de18f8aa37/tests/services/__init__.py
--------------------------------------------------------------------------------
/tests/services/pushsafer/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mqtt-tools/mqttwarn/d9d312df66b428bf36c053ebaa88e6de18f8aa37/tests/services/pushsafer/__init__.py
--------------------------------------------------------------------------------
/tests/services/pushsafer/conftest.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from tests.util import FakeResponse
4 |
5 |
6 | @pytest.fixture
7 | def mock_urlopen_success(mocker):
8 | return mocker.patch("urllib.request.urlopen", return_value=FakeResponse(data=b'{"status": 1}'))
9 |
10 |
11 | @pytest.fixture
12 | def mock_urlopen_failure(mocker):
13 | return mocker.patch("urllib.request.urlopen", return_value=FakeResponse(data=b'{"status": 6}'))
14 |
--------------------------------------------------------------------------------
/tests/services/pushsafer/test_pushsafer_common.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # (c) 2023 The mqttwarn developers
3 | """
4 | This file contains common cases for the Pushsafer plugin, independently
5 | of the used configuration layout variant (v1 vs. v2) within the `addrs` slot.
6 | """
7 | import pytest
8 |
9 | from mqttwarn.model import ProcessorItem as Item
10 | from mqttwarn.services.pushsafer import PushsaferParameters
11 | from mqttwarn.util import load_module_from_file
12 |
13 |
14 | def test_pushsafer_configuration_empty_failure(srv, caplog, mock_urlopen_success):
15 | """
16 | Test Pushsafer service fails when providing an empty `addrs` configuration slot.
17 | """
18 |
19 | module = load_module_from_file("mqttwarn/services/pushsafer.py")
20 | item = Item(addrs=None, message="⚽ Notification message ⚽")
21 | with pytest.raises(ValueError) as ex:
22 | module.plugin(srv, item)
23 | assert ex.match("Pushsafer configuration layout empty or invalid. type=NoneType")
24 |
25 |
26 | def test_pushsafer_configuration_invalid_failure(srv, caplog, mock_urlopen_success):
27 | """
28 | Test Pushsafer service fails when providing an invalid `addrs` configuration slot.
29 | """
30 |
31 | module = load_module_from_file("mqttwarn/services/pushsafer.py")
32 | item = Item(addrs=42, message="⚽ Notification message ⚽")
33 | with pytest.raises(ValueError) as ex:
34 | module.plugin(srv, item)
35 | assert ex.match("Pushsafer configuration layout empty or invalid. type=int")
36 |
37 |
38 | def test_pushsafer_parameters_to_dict():
39 | pp = PushsaferParameters(private_key="foo", device="bar")
40 | result = pp.to_dict()
41 | assert isinstance(result, dict)
42 | assert "private_key" in result
43 | assert "device" in result
44 |
--------------------------------------------------------------------------------
/tests/services/pushsafer/util.py:
--------------------------------------------------------------------------------
1 | import typing as t
2 | import urllib.request
3 | from urllib.parse import parse_qsl
4 |
5 | TEST_TOKEN = "myToken"
6 |
7 |
8 | def get_reference_data(**more_data):
9 | data = {
10 | "m": "⚽ Notification message ⚽",
11 | "k": "myToken",
12 | }
13 | data.update(more_data)
14 | return data
15 |
16 |
17 | def assert_request(request: urllib.request.Request, reference_data: t.Dict[str, str]):
18 | assert request.full_url == "https://www.pushsafer.com/api"
19 | if isinstance(request.data, bytes):
20 | payload = request.data
21 | elif hasattr(request.data, "read"):
22 | payload = request.data.read() # type: ignore[union-attr]
23 | else:
24 | raise ValueError(f"Something went wrong. Could not decode `request.data`: {request.data}")
25 | actual_data = dict(parse_qsl(payload.decode("utf-8"), keep_blank_values=True))
26 | msg = f"\nGot: {actual_data}\nExpected: {reference_data}"
27 | assert actual_data == reference_data, msg
28 |
--------------------------------------------------------------------------------
/tests/services/test_alexa.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # (c) 2021-2022 The mqttwarn developers
3 | from mqttwarn.model import ProcessorItem as Item
4 | from mqttwarn.util import load_module_from_file
5 |
6 |
7 | def test_alexa_notify_me_success(srv, mocker, caplog):
8 |
9 | module = load_module_from_file("mqttwarn/services/alexa-notify-me.py")
10 |
11 | accessCode = "myToken"
12 | item = Item(addrs=[accessCode], message="⚽ Notification message ⚽")
13 |
14 | requests_mock = mocker.patch("requests.post")
15 | outcome = module.plugin(srv, item)
16 | requests_mock.assert_called_once_with(
17 | url="https://api.notifymyecho.com/v1/NotifyMe",
18 | data='{"notification": "\\u26bd Notification message \\u26bd", "accessCode": "myToken"}',
19 | )
20 |
21 | assert outcome is True
22 | assert "Sending to NotifyMe service" in caplog.messages
23 | assert "Successfully sent to NotifyMe service" in caplog.messages
24 |
25 |
26 | def test_alexa_notify_me_real_auth_failure(srv, caplog):
27 | module = load_module_from_file("mqttwarn/services/alexa-notify-me.py")
28 |
29 | accessCode = "myToken"
30 | item = Item(addrs=[accessCode], message="⚽ Notification message ⚽")
31 |
32 | outcome = module.plugin(srv, item)
33 |
34 | assert outcome is False
35 | assert "Sending to NotifyMe service" in caplog.messages
36 | assert "Failed to send message to NotifyMe service" in caplog.text
37 |
--------------------------------------------------------------------------------
/tests/services/test_apprise_ntfy.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # (c) 2021-2023 The mqttwarn developers
3 | from unittest import mock
4 | from unittest.mock import call
5 |
6 | from mqttwarn.model import ProcessorItem as Item
7 | from mqttwarn.util import load_module_by_name
8 |
9 |
10 | @mock.patch("apprise.Apprise", create=True)
11 | @mock.patch("apprise.AppriseAsset", create=True)
12 | def test_apprise_ntfy_success(apprise_asset, apprise_mock, srv, caplog):
13 | module = load_module_by_name("mqttwarn.services.apprise_multi")
14 |
15 | item = Item(
16 | addrs=[
17 | {
18 | "baseuri": "ntfy://user:password@ntfy.example.org/topic1/topic2?email=test@example.org",
19 | }
20 | ],
21 | title="⚽ Message title ⚽",
22 | message="⚽ Notification message ⚽",
23 | data={"priority": "high", "tags": "foo,bar", "click": "https://httpbin.org/headers"},
24 | )
25 |
26 | outcome = module.plugin(srv, item)
27 |
28 | assert apprise_mock.mock_calls == [
29 | call(asset=mock.ANY),
30 | call().add(
31 | "ntfy://user:password@ntfy.example.org/topic1/topic2?email=test@example.org"
32 | "&click=https%3A%2F%2Fhttpbin.org%2Fheaders&priority=high&tags=foo%2Cbar"
33 | ),
34 | call().notify(body="⚽ Notification message ⚽", title="⚽ Message title ⚽"),
35 | call().notify().__bool__(),
36 | ]
37 |
38 | assert "Successfully sent message using Apprise" in caplog.messages
39 | assert outcome is True
40 |
--------------------------------------------------------------------------------
/tests/services/test_autoremote.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # (c) 2021-2022 The mqttwarn developers
3 | from mqttwarn.model import ProcessorItem as Item
4 | from mqttwarn.util import load_module_from_file
5 |
6 |
7 | def test_autoremote_success(srv, mocker, caplog):
8 |
9 | item = Item(
10 | target="test",
11 | addrs=["ApiKey", "Password", "Target", "Group", "TTL"],
12 | topic="autoremote/user",
13 | message="⚽ Notification message ⚽",
14 | )
15 |
16 | module = load_module_from_file("mqttwarn/services/autoremote.py")
17 |
18 | requests_mock = mocker.patch("requests.get")
19 |
20 | outcome = module.plugin(srv, item)
21 | requests_mock.assert_called_once_with(
22 | "https://autoremotejoaomgcd.appspot.com/sendmessage",
23 | params={
24 | "key": "ApiKey",
25 | "message": "⚽ Notification message ⚽",
26 | "target": "Target",
27 | "sender": "autoremote/user",
28 | "password": "Password",
29 | "ttl": "TTL",
30 | "collapseKey": "Group",
31 | },
32 | )
33 |
34 | assert outcome is True
35 | assert "Sending to autoremote service" in caplog.messages
36 | assert "Successfully sent to autoremote service" in caplog.messages
37 |
38 |
39 | def test_autoremote_failure(srv, mocker, caplog):
40 |
41 | item = Item(
42 | target="test",
43 | addrs=["ApiKey", "Password", "Target", "Group", "TTL"],
44 | topic="autoremote/user",
45 | message="⚽ Notification message ⚽",
46 | )
47 |
48 | module = load_module_from_file("mqttwarn/services/autoremote.py")
49 |
50 | requests_mock = mocker.patch("requests.get", side_effect=Exception("something failed"))
51 |
52 | outcome = module.plugin(srv, item)
53 | requests_mock.assert_called_once()
54 |
55 | assert outcome is False
56 | assert "Sending to autoremote service" in caplog.messages
57 | assert "Failed to send message to autoremote service: something failed" in caplog.messages
58 |
--------------------------------------------------------------------------------
/tests/services/test_execute.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # (c) 2023 The mqttwarn developers
3 | import sys
4 |
5 | import pytest
6 |
7 | import mqttwarn.services.execute
8 | from mqttwarn.model import ProcessorItem as Item
9 |
10 |
11 | @pytest.mark.skipif(sys.platform == "win32", reason="This test does not work on Windows")
12 | def test_execute_success(tmp_path, srv, caplog, capfd):
13 | """
14 | Dispatch a single command invocation, and verify it worked.
15 | """
16 |
17 | tmpfile = tmp_path / "spool.txt"
18 | tmpfile.write_text("Hello, world.")
19 |
20 | module = mqttwarn.services.execute
21 |
22 | item = Item(
23 | target="test",
24 | addrs=["cat", "[TEXT]"],
25 | message=str(tmpfile),
26 | data={},
27 | )
28 |
29 | outcome = module.plugin(srv, item)
30 |
31 | assert outcome is True
32 | stdout, stderr = capfd.readouterr()
33 | assert "Hello, world." == stdout
34 |
35 |
36 | def test_execute_failure(srv, caplog, capfd):
37 | """
38 | Dispatch a single command invocation with an unknown command, and verify the failure will be logged.
39 | """
40 |
41 | module = mqttwarn.services.execute
42 |
43 | item = Item(
44 | target="test",
45 | addrs=["foobar"],
46 | message="",
47 | data={},
48 | )
49 |
50 | outcome = module.plugin(srv, item)
51 |
52 | assert outcome is False
53 | if sys.platform == "win32":
54 | assert "Cannot execute ['foobar'] because [WinError 2] The system cannot find the file specified" in caplog.text
55 | else:
56 | assert "Cannot execute ['foobar'] because [Errno 2] No such file or directory: 'foobar'" in caplog.text
57 |
--------------------------------------------------------------------------------
/tests/services/test_http.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # (c) 2023 The mqttwarn developers
3 | from mqttwarn.configuration import Config
4 | from mqttwarn.core import bootstrap, load_services
5 |
6 |
7 | def test_http_urllib_load_by_alias(caplog):
8 | """
9 | Verify loading the `http` service works, even if its implementation module is called `http_urllib`.
10 | """
11 |
12 | config = Config()
13 | config.add_section("config:http")
14 | bootstrap(config=config)
15 | load_services(["http"])
16 |
--------------------------------------------------------------------------------
/tests/services/test_log.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # (c) 2022 The mqttwarn developers
3 | import json
4 |
5 | from tests import configfile_full
6 | from tests.util import core_bootstrap, send_message
7 |
8 |
9 | def test_log_invalid_target(caplog):
10 | """
11 | Verify that mqttwarn warns appropriately when evaluating
12 | topic target interpolating yields an invalid log level.
13 | """
14 |
15 | # Bootstrap the core machinery without MQTT.
16 | core_bootstrap(configfile=configfile_full)
17 |
18 | # Signal mocked MQTT message to the core machinery for processing.
19 | payload = json.dumps({"loglevel": "invalid", "message": "Foo bar"})
20 | send_message(topic="test/targets-interpolated", payload=payload)
21 |
22 | # Proof that the message has been routed to the "log" plugin properly.
23 | assert ("mqttwarn.core", 20, "Invoking service plugin for `log'") in caplog.record_tuples
24 | assert (
25 | "mqttwarn.services.log",
26 | 40,
27 | "Cannot invoke service log with level `invalid': 'invalid'",
28 | ) in caplog.record_tuples
29 | assert (
30 | "mqttwarn.core",
31 | 30,
32 | "Notification failed or timed out. service=log, topic=test/targets-interpolated",
33 | ) in caplog.record_tuples
34 |
35 |
36 | def test_log_broken_target(caplog):
37 | """
38 | Verify that mqttwarn warns appropriately when evaluating
39 | topic target interpolating does not yield a list.
40 | """
41 |
42 | # Bootstrap the core machinery without MQTT.
43 | core_bootstrap(configfile=configfile_full)
44 |
45 | # Signal mocked MQTT message to the core machinery for processing.
46 | payload = json.dumps({"loglevel": "broken", "message": "Foo bar"})
47 | send_message(topic="test/targets-interpolated", payload=payload)
48 |
49 | # Proof that the message has been routed to the "log" plugin properly.
50 | assert ("mqttwarn.core", 20, "Invoking service plugin for `log'") in caplog.record_tuples
51 | assert (
52 | "mqttwarn.core",
53 | 40,
54 | "Invoking service failed. Reason: `item.addrs` is not a list. service=log, topic=test/targets-interpolated",
55 | ) in caplog.record_tuples
56 | assert (
57 | "mqttwarn.core",
58 | 30,
59 | "Notification failed or timed out. service=log, topic=test/targets-interpolated",
60 | ) in caplog.record_tuples
61 |
--------------------------------------------------------------------------------
/tests/services/test_noop.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # (c) 2023 The mqttwarn developers
3 | from mqttwarn.model import ProcessorItem
4 | from mqttwarn.util import load_module_by_name
5 |
6 |
7 | def test_noop_success(srv, caplog):
8 | module = load_module_by_name("mqttwarn.services.noop")
9 |
10 | item = ProcessorItem()
11 | outcome = module.plugin(srv, item)
12 |
13 | assert outcome is True
14 | assert "Successfully sent message using noop" in caplog.messages
15 |
16 |
17 | def test_noop_failure(srv, caplog):
18 | module = load_module_by_name("mqttwarn.services.noop")
19 |
20 | item = ProcessorItem(message="fail")
21 | outcome = module.plugin(srv, item)
22 |
23 | assert outcome is False
24 | assert "Failed sending message using noop" in caplog.messages
25 |
--------------------------------------------------------------------------------
/tests/test_cron.py:
--------------------------------------------------------------------------------
1 | from unittest.mock import Mock, call
2 |
3 | from mqttwarn.cron import PeriodicThread
4 | from tests.util import delay
5 |
6 |
7 | def test_periodic_thread_success_now():
8 | """
9 | Proof that the `cron.PeriodicThread` implementation works as intended.
10 | """
11 | callback = Mock()
12 | pt = PeriodicThread(callback=callback, period=0.05, name="foo", srv="SRVDUMMY", now=True, options={"foo": "bar"})
13 | pt.start()
14 | delay(0.35)
15 | pt.cancel()
16 | pt.join()
17 |
18 | # Timer should have been called at least two times.
19 | assert [
20 | call("SRVDUMMY", options={"foo": "bar"}),
21 | call("SRVDUMMY", options={"foo": "bar"}),
22 | ] in callback.mock_calls
23 |
24 |
25 | def test_periodic_thread_success_not_now():
26 | """
27 | Proof that the `cron.PeriodicThread` implementation works as intended.
28 | """
29 | callback = Mock()
30 | pt = PeriodicThread(callback=callback, period=0.05, name="foo", srv="SRVDUMMY", now=False, options={"foo": "bar"})
31 | pt.start()
32 | delay(0.35)
33 | pt.cancel()
34 | pt.join()
35 |
36 | # Timer should have been called at least two times.
37 | assert [
38 | call("SRVDUMMY", options={"foo": "bar"}),
39 | call("SRVDUMMY", options={"foo": "bar"}),
40 | ] in callback.mock_calls
41 |
42 |
43 | def test_periodic_thread_failure(caplog):
44 | """
45 | Proof that the `cron.PeriodicThread` implementation croaks as intended.
46 | """
47 |
48 | def callback(srv, *args, **kwargs):
49 | assert srv == "SRVDUMMY"
50 | assert args == ()
51 | assert kwargs == {"options": {"foo": "bar"}}
52 | raise ValueError("Something failed")
53 |
54 | pt = PeriodicThread(callback=callback, period=0.005, name="foo", srv="SRVDUMMY", now=True, options={"foo": "bar"})
55 | pt.start()
56 | delay(0.0125)
57 | pt.cancel()
58 | pt.join()
59 |
60 | assert "Exception while running periodic thread 'foo'" in caplog.messages
61 | assert "ValueError: Something failed" in caplog.text
62 |
--------------------------------------------------------------------------------
/tests/test_model.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # (c) 2018-2022 The mqttwarn developers
3 | from copy import deepcopy
4 |
5 | from mqttwarn.core import make_service
6 | from mqttwarn.model import Job, ProcessorItem, Struct
7 |
8 | JOB_PRIO1 = dict(
9 | prio=1, service="service", section="section", topic="topic", payload="payload", data="data", target="target"
10 | )
11 | JOB_PRIO2 = dict(
12 | prio=2, service="service", section="section", topic="topic", payload="payload", data="data", target="target"
13 | )
14 | JOB_PRIO1_COPY = deepcopy(JOB_PRIO1)
15 |
16 |
17 | def test_make_service():
18 | """
19 | Verify creation of `Service` instance.
20 | """
21 | service = make_service(name="foo")
22 | assert ""
62 | assert struct.enum() == data
63 |
64 |
65 | def test_processoritem():
66 | item = ProcessorItem()
67 | assert item.asdict() == {
68 | "service": None,
69 | "target": None,
70 | "config": {},
71 | "addrs": [],
72 | "priority": None,
73 | "section": None,
74 | "topic": None,
75 | "title": None,
76 | "message": None,
77 | "data": None,
78 | }
79 | assert item.get("foo") is None
80 |
--------------------------------------------------------------------------------
/tests/util.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # (c) 2018-2021 The mqttwarn developers
3 | import shlex
4 | import threading
5 | import time
6 | from unittest.mock import patch
7 |
8 | import paho
9 | from paho.mqtt.client import MQTTMessage
10 |
11 | import mqttwarn
12 | from mqttwarn.commands import run as run_command
13 | from mqttwarn.configuration import load_configuration
14 | from mqttwarn.core import bootstrap, load_services, on_message, start_workers
15 |
16 |
17 | def core_bootstrap(configfile=None):
18 | """
19 | Bootstrap the core machinery without MQTT.
20 | """
21 |
22 | # If mqttwarn was already invoked beforehand, reset "exit flag".
23 | # TODO: Get rid of global variables.
24 | mqttwarn.core.exit_flag = False
25 |
26 | # Load configuration file
27 | config = load_configuration(configfile)
28 |
29 | # Bootstrap mqttwarn.core
30 | bootstrap(config=config, scriptname="testdrive")
31 |
32 | # Load services
33 | services = config.getlist("defaults", "launch")
34 | load_services(services)
35 |
36 | # Launch worker threads to operate on queue
37 | start_workers()
38 |
39 |
40 | def send_message(topic=None, payload=None, retain=False):
41 |
42 | # Mock an instance of an Eclipse Paho MQTTMessage
43 | message = MQTTMessage(mid=42, topic=topic.encode("utf-8"))
44 | if payload is not None:
45 | message.payload = payload.encode("utf-8")
46 | if retain:
47 | message.retain = True
48 |
49 | # Signal the message to the machinery
50 | on_message(None, None, message)
51 |
52 | # Give the machinery some time to process the message
53 | delay()
54 |
55 |
56 | def delay(seconds=0.075):
57 | """
58 | Wait for designated number of seconds.
59 | """
60 | threading.Event().wait(seconds)
61 |
62 |
63 | def mqtt_process(mqttc: paho.mqtt.client.Client, loops=2):
64 | """
65 | Process network events for Paho MQTT client library. Wait a bit before and after.
66 | """
67 | delay()
68 | for _ in range(loops):
69 | mqttc.loop(max_packets=10)
70 | time.sleep(0.01)
71 | delay()
72 |
73 |
74 | def invoke_command(capfd, command):
75 | if not isinstance(command, list):
76 | command = shlex.split(command)
77 | with patch("sys.argv", command):
78 | run_command()
79 | output = capfd.readouterr()
80 | return output.out, output.err
81 |
82 |
83 | class FakeResponse:
84 | """
85 | https://www.mitchellcurrie.com/blog-post/python-mock-unittesting/
86 | """
87 |
88 | status: int
89 | data: bytes
90 |
91 | def __init__(self, *, data: bytes):
92 | self.data = data
93 |
94 | def read(self):
95 | self.status = 200 if self.data is not None else 404
96 | return self.data
97 |
98 | def close(self):
99 | pass
100 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | # content of: tox.ini , put in same dir as setup.py
2 | [tox]
3 | envlist = py{27,36,37,38,39},pypy,pypy3
4 |
5 | [testenv]
6 | # install pytest in the virtualenv where commands will be executed
7 | deps = pytest
8 | commands =
9 | # NOTE: you can run any command line tool here - not just tests
10 | pytest
11 |
--------------------------------------------------------------------------------