17 Commits
0.2 ... master

Author SHA1 Message Date
Ben Roberts
a9461e20e6 Bump version to 0.5 2022-05-25 22:09:35 +01:00
Ben Roberts
b4c5ceb90e Merge pull request #4 from optiz0r/single_fuel
Support data for only a single meter
2022-05-25 22:07:55 +01:00
Ben Roberts
9e5ebeb744 Drop invalid noop keyword parameter 2022-05-25 21:52:31 +01:00
Ben Roberts
20502f14ff Handle messages which contain only electricity or gas data 2022-05-25 20:34:34 +01:00
Ben Roberts
596288161a Add noop change to kick travis 2022-05-25 20:32:45 +01:00
Ben Roberts
b7b57fb05e Add noop change to kick travis 2021-06-13 21:10:29 +01:00
Ben Roberts
3edebcf6f8 Add travis config 2021-06-13 20:51:44 +01:00
Ben Roberts
120a717efc Bump version and changelog to 0.4 2021-06-13 20:47:23 +01:00
Ben Roberts
829f7cbe58 Include dependencies in setup.py 2021-06-13 20:44:06 +01:00
Ben Roberts
e14fb13bcd Update grafana dashboard 2020-10-04 17:45:30 +01:00
Ben Roberts
bfaef6ef76 Update gitignore to omit build artifacts 2020-10-04 17:45:14 +01:00
Ben Roberts
b4c756bc40 Add grafana info to Readme 2020-09-13 13:01:09 +01:00
Ben Roberts
688f9d1088 Add grafana dashboard and screenshot 2020-09-13 12:55:15 +01:00
Ben Roberts
a67298263e Add changelog 2020-09-13 12:37:02 +01:00
Ben Roberts
d0a44b902c Merge pull request #1 from optiz0r/add-license-1
Add LICENSE
2020-09-13 12:34:28 +01:00
Ben Roberts
08f54e55ce Add LICENSE 2020-09-13 12:34:14 +01:00
Ben Roberts
0daa79454f Add Readme 2020-09-13 12:32:45 +01:00
11 changed files with 1830 additions and 35 deletions

5
.gitignore vendored
View File

@@ -1 +1,6 @@
/build/
dist/
*.egg-info/
__pycache__
config.yaml
env/

8
.travis.yml Normal file
View File

@@ -0,0 +1,8 @@
language: python
python:
- "3.6" # current default Python on Travis CI
- "3.7"
# command to install dependencies
install:
- pip install -r requirements.txt
# noop change

32
CHANGELOG.md Normal file
View File

@@ -0,0 +1,32 @@
# Changelog
## v0.5 (2022-05-25)
Improvements:
* Support messages containing data for a single meter
(electricity or gas only)
## v0.4 (2021-06-13)
Improvements:
* Added grafana dashboard
* Added dependencies to setup.py
* Published to pypi
## v0.3 (2020-09-13)
Misc:
* Add documentation
## v0.2 (2020-09-06)
Bug fixes:
* Fixed packaging of `config_default.yaml`
## v0.1 (2020-09-06)
* Initial version

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Ben Roberts
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.

56
README.md Normal file
View File

@@ -0,0 +1,56 @@
# energy-usage
Realtime energy usage reporting from Bright MQTT feed (SEP) into InfluxDB/VictoriaMetrics. Can be installed and run via pip or docker.
## Configuration
Copy `config.yaml.example` to `config.yaml` and fill in your MQTT login details, and your influx/vm server details.
The config file should be placed into one of the following locations:
* `/etc/energy-usage/config.yaml`
* `~/.config/energy-usage/config.yaml`
* Any dir pointed at by `ENERGY-USAGEDIR` env var
## Pip usage
### Installation
pip install energy-usage
### Run
```bash
energy-usage [--debug] [--noop]
```
* `--debug` enables verbose output about what the script is doing
* `--noop` mode will retrieve stats from mqtt, and show you what would be published to influx but does not actually send anything
## Docker usage
### Build
```bash
docker build -t energy-usage:latest .
```
### Run
```bash
docker run -v config.yaml:/etc/energy-usage/config.yaml energy-usage:latest
```
## Grafana
`grafana.energy-usage.json` contains an example Grafana dashboard which consumes this data (using the prometheus query interface of VictoriaMetrics).
![Grafana dashboard screenshot](energy-usage-dashboard.png)
Upon import of the dashboard, you will be prompted to select your datasource, and enter your unit and standing charges. These are used to plot the costs of realtime usage data, and the daily/weekly/monthly consumption using accumulated usage statistics by the meters. The dashboard does not currently use live tarrif data, as this is not provided in the Bright MQTT feed.
## Tested with:
* Python 3
* VictoriaMetrics 1.40
* Docker 19.03.05
* Nomad 0.12.4

BIN
energy-usage-dashboard.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

View File

@@ -1 +1 @@
VERSION = "0.1"
VERSION = "0.5"

View File

@@ -99,6 +99,7 @@ def main():
usage = parse_sep(msg.topic, msg.payload)
if usage:
usage_datapoints = usage_to_datapoints(usage)
logger.debug('Writing metrics to influx: %s', usage_datapoints)
if not config["noop"].get(bool):
influx_client.write_points(usage_datapoints)
except KeyboardInterrupt:

View File

@@ -31,30 +31,7 @@ def parse_sep(topic, payload_str):
payload = json.loads(payload_str)
try:
assert(_get_metric(["elecMtr", "0702", "03", "00"]) == 0) # kWh
assert(_get_metric(["gasMtr", "0702", "03", "01"]) == 1) # m3
assert(_get_metric(["gasMtr", "0702", "03", "12"]) == 0) # kWh
except AssertionError:
logger.warning("Received a payload without expected data")
return None
timestamp = datetime.datetime.fromtimestamp(payload["gmtime"], tz=datetime.timezone.utc)
electricity_consumption = _get_metric(["elecMtr", "0702", "04", "00"])
electricity_daily_consumption = _get_metric(["elecMtr", "0702", "04", "01"])
electricity_weekly_consumption = _get_metric(["elecMtr", "0702", "04", "30"])
electricity_monthly_consumption = _get_metric(["elecMtr", "0702", "04", "40"])
electricity_multiplier = _get_metric(["elecMtr", "0702", "03", "01"])
electricity_divisor = _get_metric(["elecMtr", "0702", "03", "02"])
electricity_meter = _get_metric(["elecMtr", "0702", "00", "00"])
electricity_mpan = _get_metric(["elecMtr", "0702", "03", "07"], str)
gas_daily_consumption = _get_metric(["gasMtr", "0702", "0C", "01"])
gas_weekly_consumption = _get_metric(["gasMtr", "0702", "0C", "30"])
gas_monthly_consumption = _get_metric(["gasMtr", "0702", "0C", "40"])
gas_multiplier = _get_metric(["gasMtr", "0702", "03", "01"])
gas_divisor = _get_metric(["gasMtr", "0702", "03", "02"])
gas_meter = _get_metric(["gasMtr", "0702", "00", "00"])
gas_mpan = _get_metric(["gasMtr", "0702", "03", "07"], str)
device_gid = _get_metric(["gid"], str)
data = {
@@ -63,7 +40,21 @@ def parse_sep(topic, payload_str):
'topic': topic,
'gid': device_gid,
},
'electricity': {
}
try:
assert(_get_metric(["elecMtr", "0702", "03", "00"]) == 0) # kWh
electricity_consumption = _get_metric(["elecMtr", "0702", "04", "00"])
electricity_daily_consumption = _get_metric(["elecMtr", "0702", "04", "01"])
electricity_weekly_consumption = _get_metric(["elecMtr", "0702", "04", "30"])
electricity_monthly_consumption = _get_metric(["elecMtr", "0702", "04", "40"])
electricity_multiplier = _get_metric(["elecMtr", "0702", "03", "01"])
electricity_divisor = _get_metric(["elecMtr", "0702", "03", "02"])
electricity_meter = _get_metric(["elecMtr", "0702", "00", "00"])
electricity_mpan = _get_metric(["elecMtr", "0702", "03", "07"], str)
data.update({'electricity': {
'tags': {
'mpan': electricity_mpan,
},
@@ -74,8 +65,23 @@ def parse_sep(topic, payload_str):
'consumption_monthly': electricity_monthly_consumption * electricity_multiplier / electricity_divisor,
'meter_reading': electricity_meter * electricity_multiplier / electricity_divisor,
},
},
'gas': {
}})
except AssertionError:
pass
try:
assert(_get_metric(["gasMtr", "0702", "03", "01"]) == 1) # m3
assert(_get_metric(["gasMtr", "0702", "03", "12"]) == 0) # kWh
gas_daily_consumption = _get_metric(["gasMtr", "0702", "0C", "01"])
gas_weekly_consumption = _get_metric(["gasMtr", "0702", "0C", "30"])
gas_monthly_consumption = _get_metric(["gasMtr", "0702", "0C", "40"])
gas_multiplier = _get_metric(["gasMtr", "0702", "03", "01"])
gas_divisor = _get_metric(["gasMtr", "0702", "03", "02"])
gas_meter = _get_metric(["gasMtr", "0702", "00", "00"])
gas_mpan = _get_metric(["gasMtr", "0702", "03", "07"], str)
data.update({'gas': {
'tags': {
'mpan': gas_mpan,
},
@@ -85,8 +91,13 @@ def parse_sep(topic, payload_str):
'consumption_monthly': gas_monthly_consumption * gas_multiplier / gas_divisor,
'meter_reading': gas_meter * gas_multiplier / gas_divisor,
},
},
}
}})
except AssertionError:
pass
if 'electricity' not in data and 'gas' not in data:
logger.warning("Received a payload without either electricity or gas data")
return None
return data
@@ -95,11 +106,12 @@ def usage_to_datapoints(usage):
datapoints = []
for utility in ['electricity', 'gas']:
datapoints.append({
"measurement": utility,
"tags": {**usage['tags'], **usage[utility]['tags']},
"time": usage['timestamp'].isoformat(),
"fields": usage[utility]['metrics'],
})
if utility in usage:
datapoints.append({
"measurement": utility,
"tags": {**usage['tags'], **usage[utility]['tags']},
"time": usage['timestamp'].isoformat(),
"fields": usage[utility]['metrics'],
})
return datapoints

1654
grafana.energy-usage.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -38,4 +38,10 @@ setup(
data_files=[
('/etc/energy-usage/config.yaml.example', ['config.yaml.example']),
],
install_requires=[
'confuse~=1.3',
'influxdb~=5.3',
'paho-mqtt~=1.5',
]
)