inital commit

This commit is contained in:
Hendrik Schutter 2024-03-24 20:08:47 +01:00
parent 2e6347214a
commit ec29ab24f0
5 changed files with 230 additions and 1 deletions

9
LICENSE Normal file
View File

@ -0,0 +1,9 @@
MIT License
Copyright (c) 2023 localhorst
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,2 +1,14 @@
# VEGAPULS-Air-exporter
# The Things Network Exporter for VEGAPULS Air
Export metrics of a VEGAPULS Air connected via TTN as a prometheus service.
## Install ##
- `mkdir /opt/ttn-vegapulsair-exporter/`
- `cd /opt/ttn-vegapulsair-exporter/`
- import `tttn-vegapulsair-exporter.py` and `config.py`
- Set the constants in `config.py`
- `chmod +x /opt/ttn-vegapulsair-exporter/ttn-vegapulsair-exporter.py`
- `chown -R prometheus /opt/ttn-vegapulsair-exporter/`
- `nano /etc/systemd/system/ttn-vegapulsair-exporter.service`
- `systemctl daemon-reload && systemctl enable --now ttn-vegapulsair-exporter.service`

12
config.py Normal file
View File

@ -0,0 +1,12 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
""" Author: Hendrik Schutter, mail@hendrikschutter.com
"""
hostName = "127.0.0.1"
serverPort = 9106
exporter_prefix = "vegapulsair_"
ttn_user = "appid@ttn"
ttn_key = "THE APP API KEY FROM TTN CONSOLE"
ttn_region = "EU1"

181
ttn-vegapulsair-exporter.py Normal file
View File

@ -0,0 +1,181 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
""" Author: Hendrik Schutter, mail@hendrikschutter.com
"""
from http.server import BaseHTTPRequestHandler, HTTPServer
import paho.mqtt.client as mqtt
from datetime import datetime
import threading
import time
import json
import sys
import config
scrape_healthy = True
startTime = datetime.now()
node_metrics = list()
mutex = threading.Lock()
request_count = 0
class RequestHandler(BaseHTTPRequestHandler):
def get_metrics(self):
global request_count
global node_metrics
global mutex
mutex.acquire()
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
self.wfile.write(bytes(config.exporter_prefix + "expoter_duration_seconds_sum " + str(int((datetime.now() - startTime).total_seconds())) + "\n", "utf-8"))
self.wfile.write(bytes(config.exporter_prefix + "exporter_request_count " + str(request_count) + "\n", "utf-8"))
self.wfile.write(bytes(config.exporter_prefix + "exporter_scrape_healthy " + str(int(scrape_healthy)) + "\n", "utf-8"))
for metric in node_metrics:
#print(metric)
self.wfile.write(bytes(config.exporter_prefix + metric + "\n", "utf-8"))
mutex.release()
def do_GET(self):
global request_count
request_count = request_count + 1
#print("Request: " + self.path)
if (self.path.startswith("/metrics")):
self.get_metrics()
else:
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
self.wfile.write(bytes("<html>", "utf-8"))
self.wfile.write(bytes("<head><title>VEGAPULS Air exporter</title></head>", "utf-8"))
self.wfile.write(bytes("<body>", "utf-8"))
self.wfile.write(bytes('<h1>ttn-vegapulsair exporter based on data from LoRaWAN TTN node.</h1>', "utf-8"))
self.wfile.write(bytes('<p><a href="/metrics">Metrics</a></p>', "utf-8"))
self.wfile.write(bytes("</body>", "utf-8"))
self.wfile.write(bytes("</html>", "utf-8"))
def update_metrics(payload, metadata):
#print("Payload: "+ str(payload))
#print("Metadata: "+ str(metadata))
global node_metrics
global mutex
global scrape_healthy
mutex.acquire()
scrape_healthy = True
node_metrics.clear()
if "Distance" in payload:
print("set Distance: " + str(float(payload["Distance"])))
node_metrics.append("distance " + str(float(payload["Distance"])))
if "Inclination_degree" in payload:
print("set Inclination_degree: " + str(int(payload["Inclination_degree"])))
node_metrics.append("inclination_degree " + str(int(payload["Inclination_degree"])))
if "MvLinProcent" in payload:
print("set MvLinProcent: " + str(int(payload["MvLinProcent"])))
node_metrics.append("linprocent " + str(int(payload["MvLinProcent"])))
if "MvProcent" in payload:
print("set MvProcent: " + str(int(payload["MvProcent"])))
node_metrics.append("procent " + str(int(payload["MvProcent"])))
if "MvScaled" in payload:
print("set MvScaled: " + str(float(payload["MvScaled"])))
node_metrics.append("scaled " + str(float(payload["MvScaled"])))
if "MvScaledUnit" in payload:
print("set MvScaledUnit: " + str(int(payload["MvScaledUnit"])))
node_metrics.append("scaled_unit " + str(int(payload["MvScaledUnit"])))
if "PacketIdentifier" in payload:
print("set PacketIdentifier: " + str(int(payload["PacketIdentifier"])))
node_metrics.append("packet_identifier " + str(int(payload["PacketIdentifier"])))
if "RemainingPower" in payload:
print("set RemainingPower: " + str(int(payload["RemainingPower"])))
node_metrics.append("remaining_power " + str(int(payload["RemainingPower"])))
if "Temperature" in payload:
print("set Temperature: " + str(int(payload["Temperature"])))
node_metrics.append("temperature " + str(int(payload["Temperature"])))
if "Unit" in payload:
print("set Unit: " + str(int(payload["Unit"])))
node_metrics.append("unit " + str(int(payload["Unit"])))
if "UnitTemperature" in payload:
print("set UnitTemperature: " + str(int(payload["UnitTemperature"])))
node_metrics.append("temperature_unit " + str(int(payload["UnitTemperature"])))
if "rssi" in metadata[0]:
print("set rssi: " + str(int(metadata[0]["rssi"])))
node_metrics.append("rssi " + str(int(metadata[0]["rssi"])))
if "channel_rssi" in metadata[0]:
print("set channel_rssi: " + str(int(metadata[0]["channel_rssi"])))
node_metrics.append("channel_rssi " + str(int(metadata[0]["channel_rssi"])))
if "snr" in metadata[0]:
print("set snr: " + str(float(metadata[0]["snr"])))
node_metrics.append("snr " + str(float(metadata[0]["snr"])))
mutex.release()
def on_connect(mqttc, obj, flags, rc):
print("\nConnected to MQTT: rc = " + str(rc))
def on_message(mqttc, obj, msg):
#print("\nMessage: " + msg.topic + " " + str(msg.qos))
parsedJSON = json.loads(msg.payload)
#print(json.dumps(parsedJSON, indent=4))
try:
uplink_message = parsedJSON["uplink_message"]
update_metrics(uplink_message["decoded_payload"], uplink_message["rx_metadata"])
except:
mutex.acquire()
scrape_healthy = False
mutex.release()
print("Unable to parse uplink")
def on_subscribe(mqttc, obj, mid, granted_qos):
print("\nSubscribed to MQTT: " + str(mid) + " " + str(granted_qos))
def poll_mqtt(mqttc):
while True:
mqttc.loop(10) # seconds timeout
def main():
print("starting ...")
mqttc = mqtt.Client()
mqttc.on_connect = on_connect
mqttc.on_subscribe = on_subscribe
mqttc.on_message = on_message
mqttc.username_pw_set(config.ttn_user, config.ttn_key)
mqttc.tls_set()
mqttc.connect(config.ttn_region.lower() + ".cloud.thethings.network", 8883, 60)
mqttc.subscribe("#", 0) # all device uplinks
# run mqtt in thread forever
poll_mqtt_thread = threading.Thread(target=poll_mqtt, args=((mqttc,)))
poll_mqtt_thread.start()
webServer = HTTPServer((config.hostName, config.serverPort), RequestHandler)
print("Server started http://%s:%s" % (config.hostName, config.serverPort))
try:
webServer.serve_forever()
except KeyboardInterrupt:
sys.exit(-1)
webServer.server_close()
print("Server stopped.")
poll_mqtt_thread.join()
if __name__ == "__main__":
main()

View File

@ -0,0 +1,15 @@
[Unit]
Description=TTN Exporter for VEGAPULS Air
After=syslog.target
After=network.target
[Service]
RestartSec=2s
Type=oneshot
User=prometheus
Group=prometheus
WorkingDirectory=/opt/ttn-vegapulsair-exporter/
ExecStart=/usr/bin/python3 /opt/ttn-vegapulsair-exporter/ttn-vegapulsair-exporter.py
[Install]
WantedBy=multi-user.target