Browse Source

Merge 5b507985ca into 5feb293c9f

pull/383/merge
Olli 1 month ago
committed by GitHub
parent
commit
8f317f6613
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 18
      tools/rpi/Dockerfile.basic
  2. 59
      tools/rpi/README.md
  3. 5
      tools/rpi/ahoy.yml.example
  4. BIN
      tools/rpi/docker-supplementals/dashboard/Selection_025.jpg
  5. BIN
      tools/rpi/docker-supplementals/dashboard/Selection_027.jpg
  6. 1678
      tools/rpi/docker-supplementals/dashboard/SolarEnergy-1667477915632.json
  7. 38
      tools/rpi/docker-supplementals/docker-compose.yml
  8. 30
      tools/rpi/docker-supplementals/prometheus/prometheus.yml
  9. 13
      tools/rpi/hoymiles/__main__.py
  10. 72
      tools/rpi/hoymiles/outputs.py
  11. 1
      tools/rpi/optional-requirements.txt
  12. 14
      tools/rpi/systemd/hoymiles.service

18
tools/rpi/Dockerfile.basic

@ -0,0 +1,18 @@
FROM python:latest
LABEL maintainer Simoliv
USER root
RUN mkdir /hoymiles_exporter
COPY . /hoymiles_exporter
WORKDIR /hoymiles_exporter
RUN pip3 install --upgrade pip
RUN ls -la
RUN pip3 install -r requirements.txt
RUN pip3 install -r optional-requirements.txt
CMD python3 -um hoymiles --verbose --log-transactions --config ahoy.yml

59
tools/rpi/README.md

@ -251,6 +251,65 @@ Use basic command line tools to get an idea what you recorded. For example:
A brief example log is supplied in the `example-logs` folder.
Prometheus Exporter
-------------------
Python exporter for https://prometheus.io/
install requirements:
```
pip install -r optional-requirements.txt
```
Systemd service:
----------------
in systemd you find a service file to (fe) run the exporter as a systemd service at boot time.
Adjust the user in it and copy it to /lib/systemd/system
Activate it:
```
sudo systemctl enable hoymiles.service
```
Start it
```
sudo service hoymiles start
```
After that, you should be able to open a website to http://ip-of-your-rpi:9233/ and see some stats
```
# HELP python_gc_objects_collected_total Objects collected during gc
# TYPE python_gc_objects_collected_total counter
python_gc_objects_collected_total{generation="0"} 304.0
python_gc_objects_collected_total{generation="1"} 124.0
python_gc_objects_collected_total{generation="2"} 0.0
```
and on the bottom :
```
string_voltage{panel="panel_3"} 39.4
# HELP string_current DC/Panel current
# TYPE string_current gauge
string_current{panel="panel_3"} 5.73
# HELP string_power DC/Panel power
# TYPE string_power gauge
string_power{panel="panel_3"} 225.4
# HELP string_energy_daily DC/Panel energy_daily
# TYPE string_energy_daily gauge
string_energy_daily{panel="panel_3"} 487.0
```
+ more stats
I added my dashboard to docker-sup../dashboard folder if you are interested
![dashboard_example1](docker-supplementals/dashboard/Selection_025.jpg)
![dashboard_example2](docker-supplementals/dashboard/Selection_027.jpg)

5
tools/rpi/ahoy.yml.example

@ -37,6 +37,11 @@ ahoy:
topic: my_DTU_name # Name of DTU - default: hoymiles/{DTU-serial}
payload: "LAST-WILL-MESSAGE: Please check my HOST and Process!"
# adding prometheus exporter
prometheus:
disabled: false
port: 9233
# Influx2 output
influxdb:
disabled: true

BIN
tools/rpi/docker-supplementals/dashboard/Selection_025.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

BIN
tools/rpi/docker-supplementals/dashboard/Selection_027.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

1678
tools/rpi/docker-supplementals/dashboard/SolarEnergy-1667477915632.json

File diff suppressed because it is too large

38
tools/rpi/docker-supplementals/docker-compose.yml

@ -0,0 +1,38 @@
version: '3.3'
services:
prometheus:
container_name: prometheus
image: prom/prometheus
volumes:
- ./prometheus:/etc/prometheus
networks:
hoymiles_net:
ipv4_address: 172.18.0.3
ports:
- 9090:9090
restart: always
grafana:
container_name: grafana
image: grafana/grafana
ports:
- 3000:3000
restart: always
volumes:
- grafana-storage:/var/lib/grafana
networks:
hoymiles_net:
ipv4_address: 172.18.0.2
volumes:
data:
grafana-storage:
networks:
hoymiles_net:
ipam:
driver: default
config:
- subnet: "172.18.0.0/24"

30
tools/rpi/docker-supplementals/prometheus/prometheus.yml

@ -0,0 +1,30 @@
global:
scrape_interval: 15s
scrape_timeout: 10s
evaluation_interval: 15s
alerting:
alertmanagers:
- static_configs:
- targets: []
scheme: http
timeout: 10s
api_version: v1
scrape_configs:
- job_name: prometheus
honor_timestamps: true
scrape_interval: 15s
scrape_timeout: 10s
metrics_path: /metrics
scheme: http
static_configs:
- targets:
- localhost:9090
- job_name: hoymiles
honor_timestamps: true
scrape_interval: 15s
scrape_timeout: 10s
metrics_path: /
scheme: http
static_configs:
- targets:
- 172.18.0.1:9233

13
tools/rpi/hoymiles/__main__.py

@ -250,12 +250,14 @@ def poll_inverter(inverter, dtu_ser, do_init, retries):
if volkszaehler_client:
volkszaehler_client.store_status(result)
if prometheus_client:
prometheus_client.store_status(result)
# check decoder object for output
if isinstance(result, hoymiles.decoders.HardwareInfoResponse):
if mqtt_client:
mqtt_client.store_status(result, topic=inverter.get('mqtt', {}).get('topic', None))
def mqtt_on_command(client, userdata, message):
"""
Handle commands to topic
@ -387,6 +389,14 @@ if __name__ == '__main__':
bucket=influx_config.get('bucket', None),
measurement=influx_config.get('measurement', 'hoymiles'))
# create prometheus - client object
prometheus_client = None
prometheus_config = ahoy_config.get('prometheus', {})
if prometheus_config and not prometheus_config.get('disabled', False):
from .outputs import PrometheusOutputPlugin
prometheus_client = PrometheusOutputPlugin(
prometheus_config)
# create VOLKSZAEHLER - client object
volkszaehler_client = None
volkszaehler_config = ahoy_config.get('volkszaehler', {})
@ -415,4 +425,3 @@ if __name__ == '__main__':
# start main-loop
main_loop(ahoy_config)

72
tools/rpi/hoymiles/outputs.py

@ -33,6 +33,78 @@ class OutputPluginFactory:
"""
raise NotImplementedError('The current output plugin does not implement store_status')
try:
from os import path
import yaml
from prometheus_client.core import GaugeMetricFamily, REGISTRY, CounterMetricFamily
from prometheus_client import CollectorRegistry, Gauge
from prometheus_client import Info
from prometheus_client import start_http_server
except ModuleNotFoundError:
pass
class PrometheusOutputPlugin(OutputPluginFactory):
current_data = None
def __init__(self, config, **params):
super().__init__(**params)
start_http_server(config.get('port'))
REGISTRY.register(self)
def collect(self):
if self.current_data:
yield CounterMetricFamily('energy_total', 'Energy Total', value=self.current_data['energy_total'])
yield GaugeMetricFamily('temperature', 'Device Temperature', value=self.current_data['temperature'])
yield GaugeMetricFamily('pf', 'Power Factor', value=self.current_data['powerfactor'])
yield GaugeMetricFamily('frequency', 'Frequency', value=self.current_data['frequency'])
# AC Data
phase_id = 0
phase_gauge = {}
phase_types = [ 'power', 'voltage', 'current' ]
for phase_type in phase_types:
for phase in self.current_data['phases']:
phase_gauge[phase_type] = GaugeMetricFamily(f'phase_{phase_type}', f'AC/Phase {phase_id} Power', labels=['phase'])
phase_gauge[phase_type].add_metric(f'phase_{phase_id}', phase[phase_type])
yield phase_gauge[phase_type]
phase_id = phase_id + 1
# DC Data
string_id = 0
types = [ "voltage", "current", "power", "energy_daily" ]
gauge={}
for string in self.current_data['strings']:
gauge['energy_total'] = CounterMetricFamily(f'string_energy_total', f'DC/Panel energy_total', labels=['panel'])
gauge['energy_total'].add_metric([f'panel_{string_id}'], string['energy_total'])
yield gauge['energy_total']
for type in types:
gauge[type] = GaugeMetricFamily(f'string_{type}', f'DC/Panel {type}', labels=['panel'])
gauge[type].add_metric([f'panel_{string_id}'], string[type])
yield gauge[type]
string_id = string_id + 1
def store_status(self, response, **params):
"""
Publish StatusResponse object
:param hoymiles.decoders.StatusResponse response: StatusResponse object
:param topic: custom mqtt topic prefix (default: hoymiles/{inverter_ser})
:type topic: str
:raises ValueError: when response is not instance of StatusResponse
"""
if not isinstance(response, StatusResponse):
raise ValueError('Data needs to be instance of StatusResponse')
data = response.__dict__()
self.current_data = response.__dict__()
serial = data["inverter_ser"]
self.collect()
class InfluxOutputPlugin(OutputPluginFactory):
""" Influx2 output plugin """
api = None

1
tools/rpi/optional-requirements.txt

@ -1 +1,2 @@
influxdb-client>=1.28.0
prometheus_client

14
tools/rpi/systemd/hoymiles.service

@ -0,0 +1,14 @@
[Unit]
Description=Hoymiles Exporter
After=network.target
[Service]
WorkingDirectory=/home/rpi/hoymiles_exporter
ExecStart=/usr/bin/bash -c 'cd /home/rpi/hoymiles_exporter ; /usr/bin/python3 -um hoymiles --config ahoy.yml'
User=rpi
KillMode=process
Restart=on-failure
[Install]
WantedBy=multi-user.target
Alias=hoymiles.service
Loading…
Cancel
Save