mirror of
https://github.com/d3vyce/teleinfo-exporter.git
synced 2025-04-01 15:53:22 +02:00
Initial commit
This commit is contained in:
commit
2e58083957
29
.github/workflows/docker-build-version.yml
vendored
Normal file
29
.github/workflows/docker-build-version.yml
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
name: Build and Push Docker Image
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
build docker:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout code
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Login to Docker registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/${{ github.repository }}:latest,${{ env.REGISTRY }}/${{ github.repository }}:${{ github.ref_name }}
|
23
.github/workflows/lint.yml
vendored
Normal file
23
.github/workflows/lint.yml
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
name: Python Lint
|
||||
on: push
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up Python 3.11
|
||||
run: |
|
||||
apt update
|
||||
apt install -y python3 python3-pip
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python3 -m pip install --upgrade pip
|
||||
pip install -r requirements.txt
|
||||
pip install pylint black isort
|
||||
- name: Lint code
|
||||
run: |
|
||||
find . -type f -name "*.py" | xargs pylint
|
||||
find . -type f -name "*.py" | xargs black --check
|
||||
find . -type f -name "*.py" | xargs isort --check
|
36
.github/workflows/pypi-build-version.yml
vendored
Normal file
36
.github/workflows/pypi-build-version.yml
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
name: Build Pypi Package
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install --yes python3-setuptools python3-wheel
|
||||
- name: Build
|
||||
run: |
|
||||
python3 -m build
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
path: ./dist
|
||||
name: dist
|
||||
pypi-publish:
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: pypi
|
||||
url: https://pypi.org/p/teleinfo_exporter/
|
||||
permissions:
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
path: ./dist
|
||||
name: dist
|
||||
- name: Publish package distributions to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
160
.gitignore
vendored
Normal file
160
.gitignore
vendored
Normal file
@ -0,0 +1,160 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/#use-with-ide
|
||||
.pdm.toml
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
5
CHANGELOG.md
Normal file
5
CHANGELOG.md
Normal file
@ -0,0 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## [1.0.0] - 12-19-2023
|
||||
|
||||
Initial release
|
8
Dockerfile
Normal file
8
Dockerfile
Normal file
@ -0,0 +1,8 @@
|
||||
FROM python:3.12-slim
|
||||
|
||||
COPY requirements.txt requirements.txt
|
||||
RUN pip3 install -r requirements.txt
|
||||
|
||||
COPY src/ /opt/
|
||||
|
||||
CMD [ "python3", "/opt/teleinfo_exporter" ]
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 d3vyce
|
||||
|
||||
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.
|
77
README.md
Normal file
77
README.md
Normal file
@ -0,0 +1,77 @@
|
||||
# Teleinfo Exporter
|
||||
|
||||
Simple prometheus exporter for Linky teleinfo.
|
||||
Teleinfo Tasmota project :
|
||||
https://github.com/NicolasBernaerts/tasmota/tree/master/teleinfo
|
||||
|
||||
## Installation
|
||||
### Pip
|
||||
```
|
||||
python3 -m pip install teleinfo-exporter
|
||||
teleinfo-exporter --help
|
||||
```
|
||||
|
||||
### Docker
|
||||
```
|
||||
docker pull teleinfo-exporter
|
||||
```
|
||||
|
||||
Minimal Docker compose:
|
||||
```yaml
|
||||
services:
|
||||
web:
|
||||
image: teleinfo_exporter:latest
|
||||
environment:
|
||||
- BROKER_HOSTNAME=10.10.0.10
|
||||
ports:
|
||||
- 8000:8000
|
||||
restart: always
|
||||
```
|
||||
|
||||
#### Architectures
|
||||
| Architecture | Available | Tag |
|
||||
| ------------ | --------- | ----------------------- |
|
||||
| x86-64 | ✅ | amd64-\<version tag\> |
|
||||
| arm64 | ✅ | arm64-\<version tag\> |
|
||||
|
||||
#### Version Tags
|
||||
| Tag | Available | Description |
|
||||
| ------ | --------- | ---------------------------------------------------- |
|
||||
| latest | ✅ | Latest version |
|
||||
|
||||
#### Variables
|
||||
| Argument | Variable | Description | Default |
|
||||
| ------------------- | -------------------- | ------------------ | ---------------------- |
|
||||
| `--http_port` | `-e HTTP_PORT` | HTTP Port | `8000` |
|
||||
| `--auth_user` | `-e AUTH_USER` | Basic Auth User | |
|
||||
| `--auth_hash` | `-e AUTH_HASH` | Basic Auth Hash | |
|
||||
| `--http_cert` | `-e HTTP_CERT` | HTTP Certificate | |
|
||||
| `--http_key` | `-e HTTP_KEY` | HTTP Key | |
|
||||
| `--broker_host` | `-e BROKER_HOST` | MQTT Host | |
|
||||
| `--broker_port` | `-e BROKER_PORT` | MQTT Port | `1883` |
|
||||
| `--broker_user` | `-e BROKER_USER` | MQTT User | |
|
||||
| `--broker_password` | `-e BROKER_PASSWORD` | MQTT Password | |
|
||||
| `--broker_topic` | `-e BROKER_TOPIC` | Teleinfo Topic | `teleinfo/tele/SENSOR` |
|
||||
|
||||
## Configuration
|
||||
### HTTP Authentication
|
||||
To generate the password hash use the following command:
|
||||
```bash
|
||||
htpasswd -bnBC 10 "" PASSWORD | tr -d ':'
|
||||
```
|
||||
|
||||
### Prometheus
|
||||
Config example:
|
||||
```yaml
|
||||
scrape_configs:
|
||||
- job_name: 'Teleinfo'
|
||||
scheme: https
|
||||
tls_config:
|
||||
ca_file: teleinfo.crt
|
||||
basic_auth:
|
||||
username: USER
|
||||
password: PASSWORD
|
||||
static_configs:
|
||||
- targets:
|
||||
- 192.168.1.2:8000
|
||||
```
|
8
compose.yml
Normal file
8
compose.yml
Normal file
@ -0,0 +1,8 @@
|
||||
services:
|
||||
web:
|
||||
image: teleinfo_exporter:latest
|
||||
environment:
|
||||
- BROKER_HOSTNAME=10.10.0.10
|
||||
ports:
|
||||
- 8000:8000
|
||||
restart: always
|
36
pyproject.toml
Normal file
36
pyproject.toml
Normal file
@ -0,0 +1,36 @@
|
||||
[project]
|
||||
name = "teleinfo-exporter"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"bcrypt ~= 4.1",
|
||||
"configargparse ~= 1.7",
|
||||
"flask ~= 3.0",
|
||||
"flask-httpauth ~= 4.8",
|
||||
"paho-mqtt ~= 1.6",
|
||||
"prometheus-client ~= 0.19",
|
||||
]
|
||||
requires-python = ">=3.8"
|
||||
authors = [
|
||||
{ name="d3vyce", email="contact@d3vyce.fr" },
|
||||
]
|
||||
description = "Simple prometheus exporter for Linky teleinfo."
|
||||
readme = "README.md"
|
||||
license = {file = "LICENSE"}
|
||||
keywords = ["teleinfo", "linky", "prometheus", "exporter"]
|
||||
classifiers = [
|
||||
"Programming Language :: Python :: 3",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Operating System :: OS Independent",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://github.com/d3vyce/teleinfo-exporter"
|
||||
Repository = "https://github.com/d3vyce/teleinfo-exporter.git"
|
||||
Issues = "https://github.com/d3vyce/teleinfo-exporter/issues"
|
||||
Changelog = "https://github.com/d3vyce/teleinfo-exporter/blob/main/CHANGELOG.md"
|
||||
|
||||
[project.scripts]
|
||||
teleinfo-exporter = "teleinfo_exporter.app:main"
|
||||
|
||||
[tool.isort]
|
||||
profile = "black"
|
6
requirements.txt
Normal file
6
requirements.txt
Normal file
@ -0,0 +1,6 @@
|
||||
bcrypt~=4.1
|
||||
configargparse~=1.7
|
||||
flask~=3.0
|
||||
flask-httpauth~=4.8
|
||||
paho-mqtt~=1.6
|
||||
prometheus-client~=0.19
|
0
src/teleinfo_exporter/__init__.py
Normal file
0
src/teleinfo_exporter/__init__.py
Normal file
8
src/teleinfo_exporter/__main__.py
Normal file
8
src/teleinfo_exporter/__main__.py
Normal file
@ -0,0 +1,8 @@
|
||||
# pylint: disable=import-error
|
||||
"""
|
||||
Teleinfo exporter for the Prometheus monitoring system
|
||||
"""
|
||||
|
||||
from app import main
|
||||
|
||||
main()
|
212
src/teleinfo_exporter/app.py
Normal file
212
src/teleinfo_exporter/app.py
Normal file
@ -0,0 +1,212 @@
|
||||
# pylint: disable=missing-module-docstring,missing-function-docstring
|
||||
|
||||
import json
|
||||
import random
|
||||
import string
|
||||
|
||||
import bcrypt
|
||||
import configargparse
|
||||
import paho.mqtt.client as mqttClient
|
||||
from flask import Flask
|
||||
from flask_httpauth import HTTPBasicAuth
|
||||
from prometheus_client import Gauge, make_wsgi_app
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config["SECRET_KEY"] = "".join(random.sample(string.ascii_lowercase, 16))
|
||||
auth = HTTPBasicAuth()
|
||||
|
||||
# ENERGY
|
||||
teleinfo_total = Gauge("teleinfo_total", "energy total")
|
||||
teleinfo_yesterday = Gauge("teleinfo_yesterday", "energy yesterday")
|
||||
teleinfo_today = Gauge("teleinfo_today", "energy today")
|
||||
teleinfo_power = Gauge("teleinfo_power", "active power")
|
||||
teleinfo_apparent_power = Gauge("teleinfo_apparent_power", "apparent power")
|
||||
teleinfo_reactive_power = Gauge("teleinfo_reactive_power", "reactive power")
|
||||
teleinfo_power_factor = Gauge("teleinfo_power_factor", "power factor")
|
||||
teleinfo_voltage = Gauge("teleinfo_voltage", "voltage")
|
||||
teleinfo_current = Gauge("teleinfo_current", "current")
|
||||
|
||||
# METER
|
||||
teleinfo_phases_count = Gauge("teleinfo_phases_count", "number of phases")
|
||||
teleinfo_max_current_per_phase = Gauge(
|
||||
"teleinfo_current_per_phase", "current per phase in the contract"
|
||||
)
|
||||
teleinfo_max_power_per_phase = Gauge(
|
||||
"teleinfo_max_power_per_phase", "power per phase in the contract (VA)"
|
||||
)
|
||||
teleinfo_max_power_per_phase_with_overload = Gauge(
|
||||
"teleinfo_max_power_per_phase_with_overload",
|
||||
"maximum power per phase including an accetable % of overload (VA)",
|
||||
)
|
||||
teleinfo_instant_voltage_per_phase = Gauge(
|
||||
"teleinfo_instant_voltage_per_phase",
|
||||
"instant voltage on phase x",
|
||||
["phase"],
|
||||
)
|
||||
teleinfo_instant_apparent_power_per_phase = Gauge(
|
||||
"teleinfo_instant_apparent_power_per_phase",
|
||||
"instant apparent power on phase x",
|
||||
["phase"],
|
||||
)
|
||||
teleinfo_instant_active_power_per_phase = Gauge(
|
||||
"teleinfo_instant_active_power_per_phase",
|
||||
"instant active power on phase x",
|
||||
["phase"],
|
||||
)
|
||||
teleinfo_instant_current_per_phase = Gauge(
|
||||
"teleinfo_instant_current_per_phase",
|
||||
"instant current on phase x",
|
||||
["phase"],
|
||||
)
|
||||
teleinfo_power_factor_per_phase = Gauge(
|
||||
"teleinfo_power_factor_per_phase",
|
||||
"current calculated power factor (cos φ) on phase x",
|
||||
["phase"],
|
||||
)
|
||||
teleinfo_total_apparent_power = Gauge(
|
||||
"teleinfo_total_apparent_power", "total instant apparent power (on all phases)"
|
||||
)
|
||||
teleinfo_total_active_power = Gauge(
|
||||
"teleinfo_total_active_power", "total instant active power (on all phases)"
|
||||
)
|
||||
teleinfo_total_current = Gauge(
|
||||
"teleinfo_total_current", "total instant current (on all phases)"
|
||||
)
|
||||
|
||||
# PROD
|
||||
teleinfo_production_instant_apparent_power = Gauge(
|
||||
"teleinfo_production_instant_apparent_power", "instant apparent power"
|
||||
)
|
||||
teleinfo_production_instant_active_power = Gauge(
|
||||
"teleinfo_production_instant_active_power", "instant active power"
|
||||
)
|
||||
teleinfo_production_power_factor = Gauge(
|
||||
"teleinfo_production_power_factor", "current calculated power factor (cos φ)"
|
||||
)
|
||||
|
||||
# TIC
|
||||
teleinfo_contract_number = Gauge(
|
||||
"teleinfo_contract_number", "contract number", ["number"]
|
||||
)
|
||||
teleinfo_contract_type = Gauge("teleinfo_contract_type", "contract type", ["type"])
|
||||
|
||||
|
||||
def on_connect(client, userdata, flags, rc): # pylint: disable=unused-argument
|
||||
if rc == 0:
|
||||
print("Connected to broker")
|
||||
else:
|
||||
print("Connection failed")
|
||||
|
||||
|
||||
def on_message(client, userdata, message): # pylint: disable=unused-argument
|
||||
message = json.loads(message.payload.decode("utf-8"))
|
||||
if "ENERGY" in message:
|
||||
teleinfo_total.set(message["ENERGY"]["Total"])
|
||||
teleinfo_yesterday.set(message["ENERGY"]["Yesterday"])
|
||||
teleinfo_today.set(message["ENERGY"]["Today"])
|
||||
teleinfo_power.set(message["ENERGY"]["Power"])
|
||||
teleinfo_apparent_power.set(message["ENERGY"]["ApparentPower"])
|
||||
teleinfo_reactive_power.set(message["ENERGY"]["ReactivePower"])
|
||||
teleinfo_power_factor.set(message["ENERGY"]["Factor"])
|
||||
teleinfo_voltage.set(message["ENERGY"]["Voltage"])
|
||||
teleinfo_current.set(message["ENERGY"]["Current"])
|
||||
elif "METER" in message:
|
||||
teleinfo_phases_count.set(message["METER"]["PH"])
|
||||
teleinfo_max_current_per_phase.set(message["METER"]["ISUB"])
|
||||
teleinfo_max_power_per_phase.set(message["METER"]["PSUB"])
|
||||
teleinfo_max_power_per_phase_with_overload.set(message["METER"]["PMAX"])
|
||||
teleinfo_total_apparent_power.set(message["METER"]["P"])
|
||||
teleinfo_total_active_power.set(message["METER"]["W"])
|
||||
teleinfo_total_current.set(message["METER"]["I"])
|
||||
for i in range(1, 4):
|
||||
if not message["METER"].get(f"U{i}"):
|
||||
break
|
||||
teleinfo_instant_voltage_per_phase.labels(f"U{i}").set(
|
||||
message["METER"][f"U{i}"]
|
||||
)
|
||||
teleinfo_instant_apparent_power_per_phase.labels(f"P{i}").set(
|
||||
message["METER"][f"P{i}"]
|
||||
)
|
||||
teleinfo_instant_active_power_per_phase.labels(f"W{i}").set(
|
||||
message["METER"][f"W{i}"]
|
||||
)
|
||||
teleinfo_instant_current_per_phase.labels(f"I{i}").set(
|
||||
message["METER"][f"I{i}"]
|
||||
)
|
||||
teleinfo_power_factor_per_phase.labels(f"C{i}").set(
|
||||
message["METER"][f"C{i}"]
|
||||
)
|
||||
|
||||
elif "PROD" in message:
|
||||
teleinfo_production_instant_apparent_power.set(message["PROD"]["VA"])
|
||||
teleinfo_production_instant_active_power.set(message["PROD"]["W"])
|
||||
teleinfo_production_power_factor.set(message["PROD"]["COS"])
|
||||
elif "TIC" in message:
|
||||
teleinfo_contract_number.labels(message["TIC"]["ADCO"]).set(0)
|
||||
teleinfo_contract_type.labels(message["TIC"]["OPTARIF"]).set(0)
|
||||
|
||||
|
||||
@app.before_request
|
||||
@auth.login_required()
|
||||
def global_auth():
|
||||
return
|
||||
|
||||
|
||||
@auth.verify_password
|
||||
def verify_password(username, password):
|
||||
if (
|
||||
not app.config.get("USERS")
|
||||
or username in app.config["USERS"]
|
||||
and bcrypt.hashpw(password.encode(), app.config["USERS"].get(username))
|
||||
):
|
||||
return username
|
||||
return None
|
||||
|
||||
|
||||
@app.route("/metrics")
|
||||
def metrics():
|
||||
return make_wsgi_app()
|
||||
|
||||
|
||||
def main():
|
||||
p = configargparse.ArgParser()
|
||||
p.add("--broker_host", required=True, help="MQTT Host", env_var="BROKER_HOST")
|
||||
p.add("--broker_port", help="MQTT Port", env_var="BROKER_PORT", default=1883)
|
||||
p.add(
|
||||
"--broker_topic",
|
||||
help="Teleinfo Topic",
|
||||
env_var="BROKER_TOPIC",
|
||||
default="teleinfo/tele/SENSOR",
|
||||
)
|
||||
p.add("--broker_user", help="MQTT user", env_var="BROKER_USER")
|
||||
p.add("--broker_password", help="MQTT Password", env_var="BROKER_PASSWORD")
|
||||
p.add("--auth_user", help="Basic Auth user", env_var="AUTH_USER")
|
||||
p.add("--auth_hash", help="Basic Auth hash", env_var="AUTH_HASH")
|
||||
p.add("--http_port", help="HTTP Server Port", env_var="HTTP_PORT", default=8000)
|
||||
p.add("--http_cert", help="HTTP Server Certificate", env_var="HTTP_CERT")
|
||||
p.add("--http_key", help="HTTP Server Key", env_var="HTTP_KEY")
|
||||
options = p.parse_args()
|
||||
print(options)
|
||||
|
||||
if options.auth_user and options.auth_hash:
|
||||
app.config["USERS"] = {options.auth_user: options.auth_hash.encode()}
|
||||
|
||||
client = mqttClient.Client(
|
||||
f"teleinfo-exporter_{''.join(random.sample(string.ascii_lowercase, 8))}"
|
||||
)
|
||||
|
||||
if options.broker_user and options.broker_password:
|
||||
client.username_pw_set(options.broker_user, password=options.broker_password)
|
||||
|
||||
client.on_connect = on_connect
|
||||
client.on_message = on_message
|
||||
client.connect(options.broker_host, port=options.broker_port)
|
||||
client.loop_start()
|
||||
|
||||
client.subscribe(options.broker_topic)
|
||||
|
||||
if options.http_cert and options.http_key:
|
||||
ssl_context = (options.http_cert, options.http_key)
|
||||
else:
|
||||
ssl_context = None
|
||||
app.run(host="0.0.0.0", port=options.http_port, ssl_context=ssl_context)
|
Loading…
x
Reference in New Issue
Block a user