diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..16d1793 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,8 @@ +[run] + omit = + test.py + */site-packages/ + tests/* + /home/travis/virtualenv/* + /usr/* + /home/buildbot/.local/lib/* diff --git a/README.md b/README.md new file mode 100644 index 0000000..cfccfde --- /dev/null +++ b/README.md @@ -0,0 +1,6 @@ +# prom433 + +![Docker Image Version (latest semver)](https://img.shields.io/docker/v/andrewjw/prom433) +[![Coverage Status](https://coveralls.io/repos/github/andrewjw/prom433/badge.svg?branch=master)](https://coveralls.io/github/andrewjw/prom433?branch=master) + +Exposes Prometheus metrics based on data received by rtl_433 diff --git a/args.txt b/args.txt new file mode 100644 index 0000000..d80d904 --- /dev/null +++ b/args.txt @@ -0,0 +1 @@ +rtl_433 -f 434000000 -s 300000 -F json diff --git a/bin/prom433 b/bin/prom433 new file mode 100644 index 0000000..aaeafa9 --- /dev/null +++ b/bin/prom433 @@ -0,0 +1,40 @@ +#!/usr/bin/env python3.8 +# prom433 +# Copyright (C) 2021 Andrew Wilkinson +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import threading +import subprocess +import sys + +from prom433 import get_arguments, connect, serve, update_stats + +def run_subprocess(args): + rtl = subprocess.run(f"rtl_433 {args.rtl} -F json", stdout=subprocess.PIPE) + + for line in rtl.stdout: + update_stats(line) + +def main(): + args = get_arguments(sys.argv[1:]) + + threading.Thread(target=run_subprocess, + args=(args, ), + daemon=True).start() + + serve(args) + +if __name__ == "__main__": + main() diff --git a/buildbot.sh b/buildbot.sh new file mode 100644 index 0000000..a135648 --- /dev/null +++ b/buildbot.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +set -e + +pip3 install -r requirements.txt + +./code_style.sh +./run_tests.sh + +BRANCH=$(git rev-parse --abbrev-ref HEAD) +echo "Building branch $BRANCH" +if [[ "$BRANCH" == "master" ]]; then + COVERALLS_REPO_TOKEN=$GLOWPROM_COVERALLS_REPO_TOKEN coveralls + semantic-release publish +fi +if [[ ${BRANCH:0:7} == "heads/v" ]]; then + ./docker_push.sh +fi diff --git a/code_style.sh b/code_style.sh new file mode 100644 index 0000000..d1a91d9 --- /dev/null +++ b/code_style.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +set -e + +pycodestyle bin/ prom433/ tests/ diff --git a/docker_push.sh b/docker_push.sh new file mode 100644 index 0000000..3cc2643 --- /dev/null +++ b/docker_push.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +set -e + +# £TRAVIS_TAG begins with a v +TAG=`echo $TRAVIS_TAG | sed 's/^.//'` + +docker login --username andrewjw --password $DOCKER_TOKEN + +docker build --build-arg VERSION=$TAG -t andrewjw/prom433:$TAG . + +docker push andrewjw/prom433:$TAG diff --git a/prom433/__init__.py b/prom433/__init__.py new file mode 100644 index 0000000..353652e --- /dev/null +++ b/prom433/__init__.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3.8 +# prom433 +# Copyright (C) 2021 Andrew Wilkinson +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from .arguments import get_arguments +from .exceptions import InvalidArguments +from .prometheus import prometheus, get_metrics +from .server import serve, update_stats + +__version__ = "0.0.1" diff --git a/prom433/arguments.py b/prom433/arguments.py new file mode 100644 index 0000000..1077633 --- /dev/null +++ b/prom433/arguments.py @@ -0,0 +1,39 @@ +# prom433 +# Copyright (C) 2021 Andrew Wilkinson +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import argparse +import os + +parser = argparse.ArgumentParser( + description='Listens to meter reports from Glow (glowmarkt.com) MQTT and' + + ' exposes them as prometheus metrics') +parser.add_argument('--rtl', type=str, nargs='?', + help='Arguments to pass to rtl_433') +parser.add_argument('--bind', type=str, nargs='?', default="0.0.0.0:9100", + help='the ip address and port to bind to') + + +def get_arguments(args): + args = parser.parse_args(args) + if "RTL_ARGS" in os.environ: + args.rtl = os.environ["RTL_ARGS"] + + if ":" not in args.bind: + args.bind = (args.bind, 9100) + else: + args.bind = (args.bind.split(":")[0], int(args.bind.split(":")[1])) + + return args diff --git a/prom433/child_process.py b/prom433/child_process.py new file mode 100644 index 0000000..0b9ae1d --- /dev/null +++ b/prom433/child_process.py @@ -0,0 +1,24 @@ +# prom433 +# Copyright (C) 2021 Andrew Wilkinson +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import subprocess + +def rtl433(args, callback, _popen=subprocess.Popen): + process = _popen(["rtl_433", "-f", "json"] + args, stdout=subprocess.PIPE) + for line in process.stdout.readlines(): + if not line.startswith("{"): + continue + callback(line) diff --git a/prom433/exceptions.py b/prom433/exceptions.py new file mode 100644 index 0000000..9db21a6 --- /dev/null +++ b/prom433/exceptions.py @@ -0,0 +1,18 @@ +# prom433 +# Copyright (C) 2021 Andrew Wilkinson +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +class InvalidArguments(Exception): + pass diff --git a/prom433/prometheus.py b/prom433/prometheus.py new file mode 100644 index 0000000..f193467 --- /dev/null +++ b/prom433/prometheus.py @@ -0,0 +1,93 @@ +# rtl_433 +# Copyright (C) 2021 Andrew Wilkinson +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import json +import logging + +METRICS = {} + +WEATHER_METRIC = "%s{id=\"%i\"} %f" + +WEATHER_TEMP_HELP = "# HELP weather_temperature The temperature in degrees celcius." +WEATHER_TEMP_TYPE = "# TYPE weather_temperature gauge" + +WEATHER_HUM_HELP = "# HELP weather_humidity The humidity in %." +WEATHER_HUM_TYPE = "# TYPE weather_humidity gauge" + +WEATHER_WIND_AVG_HELP = "# HELP weather_wind_avg The average windspeed in km/h." +WEATHER_WIND_AVG_TYPE = "# TYPE weather_wind_avg gauge" +WEATHER_WIND_MAX_HELP = "# HELP weather_wind_max The maximum windspeed in km/h." +WEATHER_WIND_MAX_TYPE = "# TYPE weather_wind_max gauge" +WEATHER_WIND_DIR_HELP = "# HELP weather_wind_dir The wind direction in degrees." +WEATHER_WIND_DIR_TYPE = "# TYPE weather_wind_dir gauge" + +WEATHER_RAIN_HELP = "# HELP weather_rain The total rainfall in mm." +WEATHER_RAIN_TYPE = "# TYPE weather_rain counter" + +WEATHER_BATTERY_HELP = "# HELP weather_rain The battery status." +WEATHER_BATTERY_TYPE = "# TYPE weather_battery gauge" + +NEXUS_METRIC = "%s{id=\"%i\", channel=\"%i\"} %f" + +NEXUS_TEMP_HELP = "# HELP nexus_temperature The temperature in degrees celcius." +NEXUS_TEMP_TYPE = "# TYPE nexus_temperature gauge" +NEXUS_HUM_HELP = "# HELP nexus_humidity The humidity in %." +NEXUS_HUM_TYPE = "# TYPE nexus_humidity gauge" +NEXUS_BATTERY_HELP = "# HELP nexus_battery The battery status." +NEXUS_BATTERY_TYPE = "# TYPE nexus_battery gauge" + +METRICS_PREFIXES = { + "weather_temperature": [WEATHER_TEMP_HELP, WEATHER_TEMP_TYPE], + "nexus_temperature": [NEXUS_TEMP_HELP, NEXUS_TEMP_TYPE], +} + +METRIC_FORMATS = { + "weather_temperature": WEATHER_METRIC, + "nexus_temperature": NEXUS_METRIC +} + +#{"time" : "2021-05-08 15:27:58", "model" : "Fineoffset-WHx080", "subtype" : 0, "id" : 202, +# "battery_ok" : 0, "temperature_C" : 6.900, "humidity" : 63, "wind_dir_deg" : 158, +# "wind_avg_km_h" : 4.896, "wind_max_km_h" : 8.568, "rain_mm" : 2.400, "mic" : "CRC"} +#{"time" : "2021-05-08 15:28:02", "model" : "Nexus-TH", "id" : 177, "channel" : 3, +# "battery_ok" : 0, "temperature_C" : 21.300, "humidity" : 39} + +def prometheus(line): + payload = json.loads(line) + + if payload["model"] == "Fineoffset-WHx080": + weather(payload) + elif payload["model"] == "Nexus-TH": + nexus(payload) + else: + model = payload["model"] + logging.warn(f"Unknown message model {model}") + +def get_metrics(): + lines = [] + for metric_name in sorted(set([m[0] for m in METRICS])): + lines.extend(METRICS_PREFIXES[metric_name]) + for metric_key, value in METRICS.items(): + if metric_key[0] == metric_name: + lines.append(METRIC_FORMATS[metric_name] % (metric_key + (value, ))) + + return "\n".join(lines) + +def weather(payload): + METRICS[("weather_temperature", payload["id"])] = payload["temperature_C"] + +def nexus(payload): + METRICS[("nexus_temperature", payload["id"], payload["channel"])] = payload["temperature_C"] diff --git a/prom433/server.py b/prom433/server.py new file mode 100644 index 0000000..09be186 --- /dev/null +++ b/prom433/server.py @@ -0,0 +1,67 @@ +# glowprom +# Copyright (C) 2020 Andrew Wilkinson +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from datetime import datetime +import http.server + +from .prometheus import prometheus + + +STATS = None + + +class Handler(http.server.BaseHTTPRequestHandler): + def do_GET(self): + if self.path == "/": + self.send_index() + elif self.path == "/metrics": + self.send_metrics() + else: + self.send_error(404) + + def send_index(self): + self.send_response(200) + self.end_headers() + self.wfile.write(""" + +RTL433 Prometheus + +

RTL433 Prometheus

+

Metrics

+ +""".encode("utf8")) + + def send_metrics(self): + if STATS is None: + self.send_response(404) + self.end_headers() + else: + self.send_response(200) + self.end_headers() + self.wfile.write(STATS.encode("utf8")) + + +def serve(args): # pragma: no cover + server = http.server.HTTPServer(args.bind, Handler) + server.serve_forever() + + +def update_stats(client, userdata, msg): + global STATS + if msg is None: + STATS = None + else: + STATS = prometheus(msg) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..1d027de --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +pycodestyle==2.7.0 +coverage==4.5.4 +python-coveralls==2.9.3 +python-semantic-release==7.15.1 diff --git a/run_tests.sh b/run_tests.sh new file mode 100755 index 0000000..86a9070 --- /dev/null +++ b/run_tests.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +coverage run test.py + +let R=$? + +coverage report + +coverage html + +exit $R diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..6d5719b --- /dev/null +++ b/setup.cfg @@ -0,0 +1,3 @@ +[semantic_release] +version_variable = prom433/__init__.py:__version__ +build_command = python3 setup.py sdist bdist_wheel diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..3265bcb --- /dev/null +++ b/setup.py @@ -0,0 +1,45 @@ +# prom433 +# Copyright (C) 2021 Andrew Wilkinson +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import setuptools + +from glowprom import __version__ + +with open("README.md", "r") as fh: + long_description = fh.read() + +with open('requirements.txt') as f: + requirements = f.read().splitlines() + +setuptools.setup( + name="prom433", + version=__version__, + author="Andrew Wilkinson", + author_email="andrewjwilkinson@gmail.com", + description="Exposes Prometheus metrics based on data received by rtl_433", + long_description=long_description, + long_description_content_type="text/markdown", + url="https://github.com/andrewjw/prom433", + packages=setuptools.find_packages(), + scripts=["bin/prom433"], + classifiers=[ + "Programming Language :: Python :: 3.8", + "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", + "Operating System :: OS Independent", + ], + python_requires='>=3.8', + install_requires=requirements, +) diff --git a/test.py b/test.py new file mode 100644 index 0000000..0b734cf --- /dev/null +++ b/test.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3.8 +# prom433 +# Copyright (C) 2021 Andrew Wilkinson +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import os +import unittest +import sys + +import tests + +def suite(): + """Test suite""" + + return unittest.defaultTestLoader.loadTestsFromModule(tests) + +if __name__ == "__main__": + RESULT = unittest.TextTestRunner(verbosity=2).run(suite()) + + sys.exit(0 if RESULT.wasSuccessful() else 1) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..ad5fc54 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,18 @@ +# prom433 +# Copyright (C) 2021 Andrew Wilkinson +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from .test_arguments import TestArguments +from .test_prometheus import TestPrometheus diff --git a/tests/output_sample.txt b/tests/output_sample.txt new file mode 100644 index 0000000..dae1649 --- /dev/null +++ b/tests/output_sample.txt @@ -0,0 +1,29 @@ +rtl_433 version 21.05-44-ga4009b6f branch master at 202107121256 inputs file rtl_tcp RTL-SDR +Use -h for usage help and see https://triq.org/ for documentation. +Trying conf file at "rtl_433.conf"... +Trying conf file at "/root/.config/rtl_433/rtl_433.conf"... +Trying conf file at "/usr/local/etc/rtl_433/rtl_433.conf"... +Trying conf file at "/etc/rtl_433/rtl_433.conf"... +Registered 161 out of 190 device decoding protocols [ 1-4 8 11-12 15-17 19-23 25-26 29-36 38-60 63 67-71 73-100 102-105 108-116 119 121 124-128 130-149 151-161 163-168 170-175 177-190 ] +Found Rafael Micro R820T tuner +[R82XX] PLL not locked! +Sample rate set to 300000 S/s. +Tuner gain set to Auto. +Tuned to 434.000MHz. +Allocating 15 zero-copy buffers +Failed to allocate zero-copy buffer for transfer 0 +Falling back to buffers in userspace +baseband_demod_FM: low pass filter for 300000 Hz at cutoff 30000 Hz, 33.3 us +{"time" : "2021-07-12 20:46:23", "model" : "Fineoffset-WHx080", "subtype" : 0, "id" : 250, "battery_ok" : 1, "temperature_C" : 15.900, "humidity" : 99, "wind_dir_deg" : 180, "wind_avg_km_h" : 0.000, "wind_max_km_h" : 0.000, "rain_mm" : 95.400, "mic" : "CRC"} +{"time" : "2021-07-12 20:46:23", "model" : "Fineoffset-WHx080", "subtype" : 0, "id" : 250, "battery_ok" : 1, "temperature_C" : 15.900, "humidity" : 99, "wind_dir_deg" : 180, "wind_avg_km_h" : 0.000, "wind_max_km_h" : 0.000, "rain_mm" : 95.400, "mic" : "CRC"} +{"time" : "2021-07-12 20:46:56", "model" : "Nexus-TH", "id" : 147, "channel" : 1, "battery_ok" : 0, "temperature_C" : 23.100, "humidity" : 62} +{"time" : "2021-07-12 20:47:11", "model" : "Fineoffset-WHx080", "subtype" : 0, "id" : 250, "battery_ok" : 1, "temperature_C" : 15.900, "humidity" : 99, "wind_dir_deg" : 180, "wind_avg_km_h" : 0.000, "wind_max_km_h" : 0.000, "rain_mm" : 95.400, "mic" : "CRC"} +{"time" : "2021-07-12 20:47:22", "model" : "Nexus-TH", "id" : 137, "channel" : 2, "battery_ok" : 0, "temperature_C" : 23.800, "humidity" : 66} +{"time" : "2021-07-12 20:47:30", "model" : "Nexus-TH", "id" : 132, "channel" : 3, "battery_ok" : 0, "temperature_C" : 23.100, "humidity" : 65} +{"time" : "2021-07-12 20:47:53", "model" : "Nexus-TH", "id" : 147, "channel" : 1, "battery_ok" : 0, "temperature_C" : 23.100, "humidity" : 62} +{"time" : "2021-07-12 20:47:59", "model" : "Fineoffset-WHx080", "subtype" : 0, "id" : 250, "battery_ok" : 1, "temperature_C" : 16.000, "humidity" : 99, "wind_dir_deg" : 180, "wind_avg_km_h" : 0.000, "wind_max_km_h" : 0.000, "rain_mm" : 95.400, "mic" : "CRC"} +{"time" : "2021-07-12 20:47:59", "model" : "Fineoffset-WHx080", "subtype" : 0, "id" : 250, "battery_ok" : 1, "temperature_C" : 16.000, "humidity" : 99, "wind_dir_deg" : 180, "wind_avg_km_h" : 0.000, "wind_max_km_h" : 0.000, "rain_mm" : 95.400, "mic" : "CRC"} +{"time" : "2021-07-12 20:48:29", "model" : "Nexus-TH", "id" : 137, "channel" : 2, "battery_ok" : 0, "temperature_C" : 23.800, "humidity" : 66} +{"time" : "2021-07-12 20:48:47", "model" : "Fineoffset-WHx080", "subtype" : 0, "id" : 250, "battery_ok" : 1, "temperature_C" : 16.000, "humidity" : 99, "wind_dir_deg" : 180, "wind_avg_km_h" : 0.000, "wind_max_km_h" : 0.000, "rain_mm" : 95.400, "mic" : "CRC"} +{"time" : "2021-07-12 20:48:49", "model" : "Nexus-TH", "id" : 132, "channel" : 3, "battery_ok" : 0, "temperature_C" : 23.100, "humidity" : 65} +{"time" : "2021-07-12 20:48:50", "model" : "Nexus-TH", "id" : 147, "channel" : 1, "battery_ok" : 0, "temperature_C" : 23.100, "humidity" : 62} diff --git a/tests/test_arguments.py b/tests/test_arguments.py new file mode 100644 index 0000000..751d131 --- /dev/null +++ b/tests/test_arguments.py @@ -0,0 +1,65 @@ +# prom433 +# Copyright (C) 2021 Andrew Wilkinson +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import os +import unittest + +from prom433 import get_arguments, InvalidArguments + + +class TestArguments(unittest.TestCase): + def setUp(self): + os.environ = {} + + def test_user_environ(self): + os.environ["GLOWPROM_USER"] = "testuser" + args = get_arguments(["--passwd", "testpassword", "--topic", "topic"]) + self.assertEqual("testuser", args.user) + self.assertEqual("testpassword", args.passwd) + + def test_passwd_environ(self): + os.environ["GLOWPROM_PASSWD"] = "testpassword" + args = get_arguments(["--user", "testuser", "--topic", "topic"]) + self.assertEqual("testpassword", args.passwd) + + def test_passwd_environ(self): + os.environ["GLOWPROM_TOPIC"] = "topic" + args = get_arguments(["--user", "testuser", + "--passwd", "testpassword"]) + self.assertEqual("topic", args.topic) + + def test_bind_without_port(self): + os.environ["GLOWPROM_USER"] = "testuser" + os.environ["GLOWPROM_PASSWD"] = "testpassword" + os.environ["GLOWPROM_TOPIC"] = "topic" + args = get_arguments(["--bind", "192.168.1.2"]) + self.assertEqual("192.168.1.2", args.bind[0]) + self.assertEqual(9100, args.bind[1]) + + def test_no_user(self): + os.environ["GLOWPROM_PASSWD"] = "testpasswd" + os.environ["GLOWPROM_TOPIC"] = "topic" + self.assertRaises(InvalidArguments, get_arguments, []) + + def test_no_passwd(self): + os.environ["GLOWPROM_USER"] = "testuser" + os.environ["GLOWPROM_TOPIC"] = "topic" + self.assertRaises(InvalidArguments, get_arguments, []) + + def test_no_topic(self): + os.environ["GLOWPROM_USER"] = "testuser" + os.environ["GLOWPROM_PASSWD"] = "testpasswd" + self.assertRaises(InvalidArguments, get_arguments, []) diff --git a/tests/test_prometheus.py b/tests/test_prometheus.py new file mode 100644 index 0000000..baa2f10 --- /dev/null +++ b/tests/test_prometheus.py @@ -0,0 +1,43 @@ +# prom433 +# Copyright (C) 2021 Andrew Wilkinson +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import io +import os +import unittest + +from prom433 import prometheus, get_metrics, child_process +from prom433.prometheus import METRICS + +MESSAGE_TEXT = open("tests/output_sample.txt", "rb").read().decode("utf8") + +def mock_popen(args, stdout): + return MockPopenReturn(io.StringIO(MESSAGE_TEXT)) + +class MockPopenReturn: + def __init__(self, buffer): + self.stdout = buffer + +class TestPrometheus(unittest.TestCase): + def test_prometheus(self): + child_process.rtl433([], prometheus, _popen=mock_popen) + + prom = get_metrics() + print(prom) + + self.assertIn( + "consumption{type=\"electricity\",period=\"daily\"} 7.971", prom) + self.assertIn( + "consumption{type=\"gas\",period=\"daily\"} 39.452", prom)