Skip to content

Commit

Permalink
Work in progress.
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewjw committed Aug 31, 2021
1 parent efb32bd commit 91edc07
Show file tree
Hide file tree
Showing 22 changed files with 604 additions and 0 deletions.
8 changes: 8 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[run]
omit =
test.py
*/site-packages/
tests/*
/home/travis/virtualenv/*
/usr/*
/home/buildbot/.local/lib/*
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions args.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
rtl_433 -f 434000000 -s 300000 -F json
40 changes: 40 additions & 0 deletions bin/prom433
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.

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()
18 changes: 18 additions & 0 deletions buildbot.sh
Original file line number Diff line number Diff line change
@@ -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
5 changes: 5 additions & 0 deletions code_style.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/bash

set -e

pycodestyle bin/ prom433/ tests/
12 changes: 12 additions & 0 deletions docker_push.sh
Original file line number Diff line number Diff line change
@@ -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
23 changes: 23 additions & 0 deletions prom433/__init__.py
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.

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"
39 changes: 39 additions & 0 deletions prom433/arguments.py
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.

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
24 changes: 24 additions & 0 deletions prom433/child_process.py
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.

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)
18 changes: 18 additions & 0 deletions prom433/exceptions.py
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.

class InvalidArguments(Exception):
pass
93 changes: 93 additions & 0 deletions prom433/prometheus.py
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.

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"]
67 changes: 67 additions & 0 deletions prom433/server.py
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.

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("""
<html>
<head><title>RTL433 Prometheus</title></head>
<body>
<h1>RTL433 Prometheus</h1>
<p><a href="/metrics">Metrics</a></p>
</body>
</html>""".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)
4 changes: 4 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pycodestyle==2.7.0
coverage==4.5.4
python-coveralls==2.9.3
python-semantic-release==7.15.1
11 changes: 11 additions & 0 deletions run_tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/bash

coverage run test.py

let R=$?

coverage report

coverage html

exit $R
3 changes: 3 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[semantic_release]
version_variable = prom433/__init__.py:__version__
build_command = python3 setup.py sdist bdist_wheel
Loading

0 comments on commit 91edc07

Please sign in to comment.