-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Convert data to prometheus metrics and serve over http.
- Loading branch information
Showing
8 changed files
with
298 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
# 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/>. | ||
|
||
import json | ||
|
||
# Taken from https://gist.github.com/ndfred/b373eeafc4f5b0870c1b8857041289a9 | ||
# Fields gathered from the ZigBee Smart Energy Standard document | ||
# 0702: Metering | ||
# - 00: Reading Information Set | ||
# - 00: CurrentSummationDelivered: meter reading | ||
# - 01: CurrentSummationReceived | ||
# - 02: CurrentMaxDemandDelivered | ||
# - 07: ReadingSnapshotTime (UTC time) | ||
# - 14: Supply Status (enum): 0x2 is on | ||
# - 02: Meter Status | ||
# - 00: Status (bit map): 10 means power quality event | ||
# - 03: Formatting | ||
# - 00: UnitofMeasure (enum): 00 means kWh, 01 means m3 | ||
# - 01: Multiplier | ||
# - 02: Divisor | ||
# - 03: SummationFormatting (bit map): | ||
# 2B means 3 digits after the decimal point, 2 digits before. | ||
# FB means 3 digits after the decimal point, 16 digits before. | ||
# no leading zeros | ||
# - 04: DemandFormatting | ||
# - 06: MeteringDeviceType: 00 means Electric Metering, | ||
# 80 means Mirrored Gas Metering | ||
# - 07: SiteID: MPAN encoded in UTF-8 | ||
# - 08: MeterSerialNumber (string) | ||
# - 12: AlternativeUnitofMeasure (enum) | ||
# - 04: Historical Consumption | ||
# - 00: InstantaneousDemand (signed): current consumption | ||
# - 01: CurrentDayConsumptionDelivered | ||
# - 30: CurrentWeekConsumptionDelivered | ||
# - 40: CurrentMonthConsumptionDelivered | ||
# - 0C: Alternative Historical Consumption | ||
# - 01: CurrentDayConsumptionDelivered | ||
# - 30: CurrentWeekConsumptionDelivered | ||
# - 40: CurrentMonthConsumptionDelivered | ||
# 0705: Prepayment | ||
# - 00: Prepayment Information Set | ||
# - 00: PaymentControlConfiguration (bit map) | ||
# - 01: CreditRemaining (signed) | ||
# 0708: Device Management | ||
# - 01: Supplier Control Attribute Set | ||
# - 01: ProviderName (string) | ||
|
||
|
||
def prometheus(msg): | ||
# Code adapted from | ||
# https://gist.github.com/ndfred/b373eeafc4f5b0870c1b8857041289a9 | ||
payload = json.loads(msg.payload) | ||
|
||
elecMtr = payload["elecMtr"]["0702"] | ||
|
||
elec_consumption = int(elecMtr["04"]["00"], 16) | ||
elec_daily_consumption = int(elecMtr["04"]["01"], 16) | ||
elec_weekly_consumption = int(elecMtr["04"]["30"], 16) | ||
elec_monthly_consumption = int(elecMtr["04"]["40"], 16) | ||
elec_multiplier = int(elecMtr["03"]["01"], 16) | ||
elec_divisor = float(int(elecMtr["03"]["02"], 16)) | ||
elec_meter = int(elecMtr["00"]["00"], 16) | ||
|
||
gasMtr = payload["gasMtr"]["0702"] | ||
|
||
gas_daily_consumption = int(gasMtr["0C"]["01"], 16) | ||
gas_weekly_consumption = int(gasMtr["0C"]["30"], 16) | ||
gas_monthly_consumption = int(gasMtr["0C"]["40"], 16) | ||
gas_multiplier = int(gasMtr["03"]["01"], 16) | ||
gas_divisor = float(int(gasMtr["03"]["02"], 16)) | ||
gas_meter = int(gasMtr["00"]["00"], 16) | ||
|
||
elec_daily_consumption = elec_daily_consumption * \ | ||
elec_multiplier / elec_divisor | ||
elec_weekly_consumption = elec_weekly_consumption * \ | ||
elec_multiplier / elec_divisor | ||
electricity_monthly_consumption = elec_monthly_consumption * \ | ||
elec_multiplier / elec_divisor | ||
electricity_meter = elec_meter * elec_multiplier / elec_divisor | ||
gas_daily_consumption = gas_daily_consumption * \ | ||
gas_multiplier / gas_divisor | ||
gas_weekly_consumption = gas_weekly_consumption * \ | ||
gas_multiplier / gas_divisor | ||
gas_monthly_consumption = gas_monthly_consumption * \ | ||
gas_multiplier / gas_divisor | ||
gas_meter = gas_meter * gas_multiplier / gas_divisor | ||
|
||
return f""" | ||
consumption{{type='electricity',period='daily'}} {elec_daily_consumption} | ||
consumption{{type='electricity',period='weekly'}} {elec_weekly_consumption} | ||
consumption{{type='electricity',period='monthly'}} {elec_monthly_consumption} | ||
meter{{type='electricity'}} {electricity_meter} | ||
consumption{{type='gas',period='daily'}} {gas_daily_consumption} | ||
consumption{{type='gas',period='weekly'}} {gas_weekly_consumption} | ||
consumption{{type='gas',period='monthly'}} {gas_monthly_consumption} | ||
meter{{type='gas'}} {gas_meter} | ||
""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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>Glow Prometheus</title></head> | ||
<body> | ||
<h1>Glow 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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{"elecMtr":{"0702":{"03":{"01":"00000001","04":"00","02":"000003E8","07":"1023497978063","03":"2B","08":"","00":"00","06":"00"},"00":{"07":"00000000","01":"000000000000","00":"0000004D64CC","14":"02","02":"000000000000"},"04":{"01":"001F23","40":"011C3C","30":"004A12","00":"000009F3"},"02":{"00":"00"}},"0705":{"00":{"01":"00000000","00":"0C94"}},"0708":{"01":{"01":"NPOWER NORTHERN"}}},"gasMtr":{"0702":{"03":{"01":"00000001","12":"00","02":"000003E8","07":"7443608110","03":"2B","08":"","00":"01","06":"80"},"00":{"00":"00000022D37B","14":"02"},"0C":{"01":"009A1C","40":"0440B4","30":"015DF5"},"02":{"00":"00"}},"0705":{"00":{"01":"00000000","00":"0C94"}},"0708":{"01":{"01":""}}},"ts":"2020-10-06 20:53:16","hversion":"GLOW-IHD-01-1v4-SMETS2","time":"5F7CD93C","zbSoftVer":"1.2.5","gmtime":1602017596,"pan":{"rssi":"BF","status":"joined","nPAN":"00","join":"0","lqi":"8C"},"smetsVer":"SMETS2","ets":"2000-01-01 00:00:00","gid":"0123456789ABCDEF"} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
# 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/>. | ||
|
||
import os | ||
import unittest | ||
|
||
from glowprom import prometheus | ||
|
||
|
||
MESSAGE_TEXT = open("tests/test_message.txt", "rb").read() | ||
|
||
|
||
class MockMessage: | ||
payload = MESSAGE_TEXT | ||
|
||
|
||
class TestPrometheus(unittest.TestCase): | ||
def test_prometheus(self): | ||
prom = prometheus(MockMessage()) | ||
|
||
self.assertIn("consumption{type='electricity',period='daily'} 7.971", | ||
prom) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
# 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/>. | ||
|
||
import io | ||
import json | ||
from datetime import datetime, timedelta | ||
import unittest | ||
|
||
from glowprom.server import Handler, update_stats | ||
|
||
MESSAGE_TEXT = open("tests/test_message.txt", "rb").read() | ||
|
||
|
||
class MockMessage: | ||
payload = MESSAGE_TEXT | ||
|
||
|
||
class MockHandler(Handler): | ||
def __init__(self): | ||
self.wfile = io.BytesIO() | ||
self.requestline = "GET" | ||
self.client_address = ("127.0.0.1", 8000) | ||
self.request_version = "1.0" | ||
self.command = "GET" | ||
|
||
|
||
class TestServer(unittest.TestCase): | ||
def setUp(self): | ||
update_stats(None, None, None) | ||
|
||
def test_index(self): | ||
handler = MockHandler() | ||
handler.path = "/" | ||
handler.do_GET() | ||
|
||
handler.wfile.seek(0) | ||
self.assertTrue("/metrics" in handler.wfile.read().decode("utf8")) | ||
|
||
def test_error(self): | ||
handler = MockHandler() | ||
handler.path = "/error" | ||
handler.do_GET() | ||
|
||
handler.wfile.seek(0) | ||
self.assertTrue("404" in handler.wfile.read().decode("utf8")) | ||
|
||
def test_metrics(self): | ||
update_stats(None, None, MockMessage()) | ||
|
||
handler = MockHandler() | ||
handler.path = "/metrics" | ||
handler.do_GET() | ||
|
||
handler.wfile.seek(0) | ||
self.assertTrue( | ||
"consumption" in handler.wfile.read().decode("utf8")) | ||
|
||
def test_metrics_before_update(self): | ||
handler = MockHandler() | ||
handler.path = "/metrics" | ||
handler.do_GET() | ||
|
||
handler.wfile.seek(0) | ||
self.assertTrue("404" in handler.wfile.read().decode("utf8")) |