Compare commits

..

5 Commits
main ... v1.0.0

Author SHA1 Message Date
3b079a6a9d fix action + update readme 2023-12-19 23:02:21 +01:00
48ff516af8 fix action 2023-12-19 22:54:48 +01:00
fd963ebb59 fix actions 2023-12-19 22:46:27 +01:00
d246b7f8b7 fix build package/image 2023-12-19 22:35:55 +01:00
838e7aadaa fix lint action 2023-12-19 22:26:20 +01:00
8 changed files with 105 additions and 133 deletions

View File

@ -1,64 +0,0 @@
name: Build Package/Image
on:
release:
types: [published]
jobs:
build-image:
runs-on: ubuntu-latest
steps:
- name: checkout code
uses: actions/checkout@v4.1.1
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v5.1.0
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ghcr.io/${{ github.repository }}:latest,ghcr.io/${{ github.repository }}:${{ github.ref_name }}
build-package:
runs-on: ubuntu-latest
steps:
- name: checkout code
uses: actions/checkout@v4.1.1
- name: Set up Python
uses: actions/setup-python@v5
- name: Install dependencies
run: |
python3 -m pip install --upgrade pip
python3 -m pip install --upgrade build
- name: Build
run: python3 -m build
- uses: actions/upload-artifact@v4.3.1
with:
path: ./dist
name: dist
pypi-publish:
runs-on: ubuntu-latest
needs: build-package
environment:
name: pypi
url: https://pypi.org/p/teleinfo_exporter/
permissions:
id-token: write
steps:
- uses: actions/download-artifact@v4.1.2
with:
path: ./dist
name: dist
- name: Publish package distributions to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
user: __token__
password: ${{ secrets.PYPI_TOKEN }}

View 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: ghcr.io
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: ghcr.io/${{ github.repository }}:latest,ghcr.io/${{ github.repository }}:${{ github.ref_name }}

View File

@ -6,7 +6,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out repository code
uses: actions/checkout@v4.1.1
uses: actions/checkout@v3
- name: Set up Python 3.11
run: |
sudo apt update

View File

@ -0,0 +1,38 @@
name: Build Pypi Package
on:
release:
types: [published]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: checkout code
uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v2
- name: Install dependencies
run: |
python3 -m pip install --upgrade pip
python3 -m pip install --upgrade build
- 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

View File

@ -1,12 +1,5 @@
# Changelog
## 1.2.0 - 04-02-2024
- fix: catch UnicodeDecodeError when loading json from broker
- fix: use .get() to avoid KeyError
## [1.0.0] - 12-19-2023
## 1.1.0 - 02-11-2024
- add: Grafana dashboard to README
- fix: MQTT broker disconnection when client connection timeout
## 1.0.0 - 12-19-2023
Initial release

View File

@ -1,12 +1,8 @@
# Teleinfo Exporter
![Grafana Dashboard](https://grafana.com/api/dashboards/20182/images/15332/image)
Simple prometheus exporter for Linky teleinfo.
[Teleinfo Tasmota project](https://github.com/NicolasBernaerts/tasmota/tree/master/teleinfo)
[Grafana Dashboard](https://grafana.com/grafana/dashboards/20182-linky-teleinfo/)
Teleinfo Tasmota project :
https://github.com/NicolasBernaerts/tasmota/tree/master/teleinfo
## Installation
### Pip

View File

@ -1,6 +1,6 @@
[project]
name = "teleinfo-exporter"
version = "1.2.0"
version = "1.0.0"
dependencies = [
"bcrypt ~= 4.1",
"configargparse ~= 1.7",

View File

@ -3,7 +3,6 @@
import json
import random
import string
import time
import bcrypt
import configargparse
@ -93,7 +92,6 @@ teleinfo_contract_type = Gauge("teleinfo_contract_type", "contract type", ["type
def on_connect(client, userdata, flags, rc): # pylint: disable=unused-argument
client.subscribe("teleinfo/tele/SENSOR")
if rc == 0:
print("Connected to broker")
else:
@ -101,71 +99,51 @@ def on_connect(client, userdata, flags, rc): # pylint: disable=unused-argument
def on_message(client, userdata, message): # pylint: disable=unused-argument
try:
message = json.loads(message.payload.decode("utf-8"))
except UnicodeDecodeError:
pass
message = json.loads(message.payload.decode("utf-8"))
if "ENERGY" in message:
teleinfo_total.set(message.get("ENERGY", {}).get("Total", 0))
teleinfo_yesterday.set(message.get("ENERGY", {}).get("Yesterday", 0))
teleinfo_today.set(message.get("ENERGY", {}).get("Today", 0))
teleinfo_power.set(message.get("ENERGY", {}).get("Power", 0))
teleinfo_apparent_power.set(message.get("ENERGY", {}).get("ApparentPower", 0))
teleinfo_reactive_power.set(message.get("ENERGY", {}).get("ReactivePower", 0))
teleinfo_power_factor.set(message.get("ENERGY", {}).get("Factor", 0))
teleinfo_voltage.set(message.get("ENERGY", {}).get("Voltage", 0))
teleinfo_current.set(message.get("ENERGY", {}).get("Current", 0))
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.get("METER", {}).get("PH", 0))
teleinfo_max_current_per_phase.set(message.get("METER", {}).get("ISUB", 0))
teleinfo_max_power_per_phase.set(message.get("METER", {}).get("PSUB", 0))
teleinfo_max_power_per_phase_with_overload.set(
message.get("METER", {}).get("PMAX", 0)
)
teleinfo_total_apparent_power.set(message.get("METER", {}).get("P", 0))
teleinfo_total_active_power.set(message.get("METER", {}).get("W", 0))
teleinfo_total_current.set(message.get("METER", {}).get("I", 0))
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.get("METER", {}).get(f"U{i}"):
if not message["METER"].get(f"U{i}"):
break
teleinfo_instant_voltage_per_phase.labels(f"U{i}").set(
message.get("METER", {}).get(f"U{i}", 0)
message["METER"][f"U{i}"]
)
teleinfo_instant_apparent_power_per_phase.labels(f"P{i}").set(
message.get("METER", {}).get(f"P{i}", 0)
message["METER"][f"P{i}"]
)
teleinfo_instant_active_power_per_phase.labels(f"W{i}").set(
message.get("METER", {}).get(f"W{i}", 0)
message["METER"][f"W{i}"]
)
teleinfo_instant_current_per_phase.labels(f"I{i}").set(
message.get("METER", {}).get(f"I{i}", 0)
message["METER"][f"I{i}"]
)
teleinfo_power_factor_per_phase.labels(f"C{i}").set(
message.get("METER", {}).get(f"C{i}", 0)
message["METER"][f"C{i}"]
)
elif "PROD" in message:
teleinfo_production_instant_apparent_power.set(
message.get("PROD", {}).get("VA", 0)
)
teleinfo_production_instant_active_power.set(
message.get("PROD", {}).get("W", 0)
)
teleinfo_production_power_factor.set(message.get("PROD", {}).get("COS", 0))
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.get("TIC", {}).get("ADCO", 0)).set(0)
teleinfo_contract_type.labels(message.get("TIC", {}).get("OPTARIF", 0)).set(0)
def on_disconnect(client, userdata, rc): # pylint: disable=unused-argument
print("Diconnected from broker, reconnecting...")
while True:
try:
if not client.reconnect():
break
except ConnectionRefusedError:
time.sleep(1)
teleinfo_contract_number.labels(message["TIC"]["ADCO"]).set(0)
teleinfo_contract_type.labels(message["TIC"]["OPTARIF"]).set(0)
@app.before_request
@ -208,6 +186,7 @@ def main():
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()}
@ -219,12 +198,13 @@ def main():
if options.broker_user and options.broker_password:
client.username_pw_set(options.broker_user, password=options.broker_password)
client.connect(options.broker_host, port=options.broker_port)
client.on_connect = on_connect
client.on_message = on_message
client.on_disconnect = on_disconnect
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: