Compare commits

...

10 Commits

Author SHA1 Message Date
LeClubber
09616dd208 Authentification par token suite modification API Netatmo 2023-09-09 20:54:10 +02:00
LeClubber
7aa8f97913 Erreur si aucun module 2022-03-27 19:20:38 +02:00
LeClubber
cf62d1162b Print que les erreurs 2022-03-13 20:01:42 +01:00
LeClubber
42648a44aa Update README 2022-03-13 18:39:38 +01:00
LeClubber
cae4f71b99 Suppression du pull 2022-03-13 18:24:33 +01:00
LeClubber
6c0bea5a80 Refresh du token 2022-03-13 18:23:44 +01:00
LeClubber
2f1769a3c4 Ajout du nom du volet par défaut 2022-03-13 17:27:43 +01:00
LeClubber
8f37c15e07 TODO important avant de déployer la première image 2022-03-13 11:45:26 +01:00
LeClubber
3d97778eb0 Ajout du README 2022-03-13 11:43:58 +01:00
LeClubber
77727dc166 It works ! 2022-03-13 11:15:21 +01:00
7 changed files with 265 additions and 62 deletions

137
README.md
View File

@@ -1 +1,136 @@
# iDiamant2Mqtt
# iDiamant2Mqtt
Outil de conversion de l'API du iDiamant (volets Bubendorff) depuis MQTT pour Home Assistant.
## Netatmo
Pour que cela fonctionne, les volets doivent être configurés dans l'application "Home + Control" de Legrand - Netatmo - Bticino.
Rendez-vous ensuite sur la page https://dev.netatmo.com/ et connectez-vous avec les même identifiants que l'application. Il faut enuite cliquer sur son login puis "My apps".
Cliquer ensuite sur "Create" et renseiger les champs suivants :
- app name : le nom de votre application, par exemple "Volets"
- description : une courte descrition, pzr exemple "Gestion des volets depuis HASS"
- data protection officer name : votre nom
- data protection officer email : l'email de votre compte
Cliquer sur "I agree to the terms and conditions" et sur Save.
Dans la partie "App Technical Parameters", conserver les identifiants "client ID" et "client secret" pour le déploiement.
Il faut ensuite générer un token en allant dans la dernière partie "Token generator" :
- Dans "Choose scopes" sélectionner :
- read_bubendorff
- write_bubendorff
- Cliquer sur "Generate Token"
- Noter les champs "Access Token" et "Refresh Token" pour le déploiement
## Déploiement
Il vous faut :
- Home-Assistant (en même temps, vous êtes surtout là pour lui non?)
- Un brocker (serveur) MQTT (sans certificat pour l'instant)
- Le service iDiamant2Mqtt
Il y a deux solutions pour déployer ce service :
- Docker (recommandé)
- Exécuter le script python directement
### Docker
Je préfère cette solution car elle encapsule le processus, contient toutes les dépendances et facilite le déploiement.
Le service peut être démarré grâce à la commande suivante :
``` sh
docker run -d --name idiamant \
-e IDIAMANT_CLIENT_ID=abcde \
-e IDIAMANT_CLIENT_SECRET=abcdefgh \
-e IDIAMANT_ACCESS_TOKEN=accesstokennetatmo \
-e IDIAMANT_REFRESH_TOKEN=refreshtokennetatmo \
leclubber/idiamant2mqtt
```
Ou en docker-compose (recommandé) :
``` yaml
version: '3'
services:
idiamant:
container_name: idiamant
image: leclubber/idiamant2mqtt
privileged: true
restart: always
environment:
- MQTT_PORT=1883
- MQTT_HOST=mqtt
- MQTT_TOPIC=homeassistant
- IDIAMANT_CLIENT_ID=<client ID généré>
- IDIAMANT_CLIENT_SECRET=<client secret généré>
- IDIAMANT_ACCESS_TOKEN=<access token généré>
- IDIAMANT_REFRESH_TOKEN=<refresh token généré>
```
Certaines variables d'environnement sont optionnelles, elles possèdent une valeur par défaut. Toutes les variables d'environnement sont les suivantes :
- MQTT_PORT (1883 par défaut)
- MQTT_HOST (mqtt par défaut)
- MQTT_TOPIC (homeassistant par défaut)
- MQTT_USER (vide par défaut)
- MQTT_PASSWORD (vide par défaut)
- IDIAMANT_CLIENT_ID (aucun par défaut)
- IDIAMANT_CLIENT_SECRET (aucun par défaut)
- IDIAMANT_ACCESS_TOKEN (aucun par défaut)
- IDIAMANT_REFRESH_TOKEN (aucun par défaut)
Un fichier [docker-compose.yml](docker-compose.yml) est disponible pour exemple, avec toutes les variables d'environnement ainsi que les services homeassistant et mqtt.
Une fois votre fichier docker-compose.yml réalisé, il faut lancer la commande suivante pour démarrer le ou les services configurés :
``` sh
docker-compose up -d
```
### Python
Il faut récupérer le contenu du dossier [idiamant](idiamant) et le mettre sur votre futur serveur.
Certaines variables d'environnement sont optionnelles, elles possèdent une valeur par défaut (voir section Docker).
Chaque variable sera définie de cette manière :
``` sh
export ENV_VAR=valeur
```
Exécuter ensuite les lignes suivantes :
``` sh
pip install -r requirements.txt
chmod +x *.py
./server.py
```
## Configuration de Home-Assistant
Le iDiamant dans Home-Assistant fonctionne avec le discovery de MQTT. Il suffit de s'abonner dans Home-Assistant au canal souhaité ("homeassistant" par défaut).
**Optionnel :** On peut également le déclarer via le fichier de configuration [configuration.yaml](configuration.yaml). Il faut ajouter un module "cover" de cette façon :
``` yaml
cover:
TODO
```
Vous pourrez alors :
- Monter, descendre et arrêter les volets
- Renommer dans Home-Assistant chaque volet
## Todo list
- [x] Initialisation des config dans MQTT
- [x] Discovery MQTT
- [x] Documentation
- [x] Utilisation d'un login/mot de passe pour le broker MQTT
- [x] Publier image Docker en multiple arch
- [x] Renouvellement du token
- [ ] Attente du serveur MQTT si non disponible
- [ ] Tester les paramètres et gestion d'erreur
- [ ] Utilisation de certificat pour le broker MQTT

View File

@@ -25,17 +25,16 @@ services:
idiamant:
container_name: idiamant
# image: leclubber/idiamant2mqtt
build: ./idiamant
image: leclubber/idiamant2mqtt
# build: ./idiamant
privileged: true
restart: always
environment:
- MQTT_PORT=1883
- MQTT_HOST=mqtt
- MQTT_TOPIC=homeassistant
- IDIAMANT_USER=${USER}
- IDIAMANT_PASSWORD=${PASSWORD}
- IDIAMANT_CLIENT_ID=${CLIENT_ID}
- IDIAMANT_CLIENT_SECRET=${CLIENT_SECRET}
- IDIAMANT_PULL_STATUS=5
- IDIAMANT_ACCESS_TOKEN=${ACCESS_TOKEN}
- IDIAMANT_REFRESH_TOKEN=${REFRESH_TOKEN}

View File

@@ -3,7 +3,6 @@ FROM python:3-alpine
ENV MQTT_PORT 1883
ENV MQTT_HOST mqtt
ENV MQTT_TOPIC homeassistant
ENV IDIAMANT_PULL_STATUS 5
WORKDIR /usr/src/idiamant
COPY requirements.txt requirements.txt

View File

@@ -12,8 +12,8 @@ class Constantes():
mqttTopic = os.getenv('MQTT_TOPIC', "homeassistant")
mqttUser = os.getenv('MQTT_USER')
mqttPassword = os.getenv('MQTT_PASSWORD')
idiamantUser = os.getenv("IDIAMANT_USER")
idiamantPassword = os.getenv("IDIAMANT_PASSWORD")
idiamantClientId = os.getenv("IDIAMANT_CLIENT_ID")
idiamantClientSecret = os.getenv("IDIAMANT_CLIENT_SECRET")
idiamantPullStatus = int(os.getenv("IDIAMANT_PULL_STATUS", 5))
idiamantAccessToken = os.getenv("IDIAMANT_ACCESS_TOKEN")
idiamantRefreshToken = os.getenv("IDIAMANT_REFRESH_TOKEN")
idiamantExpireToken = 60

View File

@@ -10,44 +10,51 @@ from time import sleep
class iDiamant():
access_token = ""
refresh_token = ""
liste_home_id = list()
access_token = Constantes.idiamantAccessToken
refresh_token = Constantes.idiamantRefreshToken
expire_token = 120
volets = {}
@staticmethod
def getToken():
""" Récupération du token sepuis Netatmo """
url = "https://api.netatmo.com/oauth2/token"
data = {'grant_type': 'password',
'username': Constantes.idiamantUser,
'password': Constantes.idiamantPassword,
'client_id': Constantes.idiamantClientId,
'client_secret': Constantes.idiamantClientSecret,
'scope': 'read_bubendorff write_bubendorff'
}
response = requests.post(url, data)
while 200 != response.status_code:
attente = 20
print("Problème d'accès au token : attente de " + attente + " secondes")
sleep(attente)
response = requests.post(url, data)
jsonStatus = json.loads(response.text)
iDiamant.access_token = jsonStatus['access_token']
iDiamant.refresh_token = jsonStatus['refresh_token']
def initDiscovery():
""" Récupération de tous les volets depuis l'API Netatmo """
url = "https://api.netatmo.com/api/homesdata"
headers = {"Authorization": "Bearer " + iDiamant.access_token}
response = requests.get(url, headers=headers)
jsonStatus = json.loads(response.text)
homes = jsonStatus['body']['homes']
for home in homes:
iDiamant.liste_home_id.append(home['id'])
if 'modules' in home:
home_id = home['id']
modules = home['modules']
for module in modules:
if "NBR" == module['type']:
iDiamant.volets[module['id']] = {
'name':module['name'],
'bridge':module['bridge'],
'id_home':home_id
}
@staticmethod
def updateToken():
""" Update d'un token en fin de vie """
url = "https://api.netatmo.com/oauth2/token"
data = {'grant_type': 'refresh_token',
'refresh_token': iDiamant.refresh_token,
'client_id': Constantes.idiamantClientId,
'client_secret': Constantes.idiamantClientSecret
}
response = requests.post(url, data)
while 200 != response.status_code:
attente = 20
print("Problème d'accès au renouvellement de token : attente de " + attente + " secondes")
sleep(attente)
response = requests.post(url, data)
jsonStatus = json.loads(response.text)
iDiamant.access_token = jsonStatus['access_token']
iDiamant.refresh_token = jsonStatus['refresh_token']
iDiamant.expire_token = int(jsonStatus['expires_in'])
@staticmethod
def publish(topic, playload, retain=True):
@@ -60,20 +67,13 @@ class iDiamant():
client.disconnect()
@staticmethod
def initDiscovery():
""" Publication des config pour discovery """
for home_id in iDiamant.liste_home_id:
url = "https://api.netatmo.com/api/homestatus?home_id=" + home_id
headers = {"Authorization": "Bearer " + iDiamant.access_token}
response = requests.get(url, headers=headers)
jsonStatus = json.loads(response.text)
modules = jsonStatus['body']['home']['modules']
for module in modules:
if "NBR" == module['type']:
id_volet = module['id']
topic = Constantes.mqttTopic + "/cover/" + id_volet + "/config"
payload = '{'
payload += '"command_topic": "' + Constantes.mqttTopic + '/cover/' + id_volet + '/set",'
payload += '"unique_id": "' + id_volet + '"'
payload += '}'
iDiamant.publish(topic, payload)
def initMqtt():
""" Publication des config sur Mqtt """
for volet in iDiamant.volets:
topic = Constantes.mqttTopic + "/cover/" + volet + "/config"
payload = '{'
payload += '"unique_id": "' + volet + '",'
payload += '"name": "' + iDiamant.volets[volet]['name'] + '",'
payload += '"command_topic": "' + Constantes.mqttTopic + '/cover/' + volet + '/set"'
payload += '}'
iDiamant.publish(topic, payload)

70
idiamant/mqtt2idiamant.py Normal file
View File

@@ -0,0 +1,70 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Author: Jérémy BRAUD
import paho.mqtt.client as mqtt
import requests
from const import Constantes
from threading import Thread
from idiamant import iDiamant
class Mqtt2iDiamant(Thread):
""" Thread chargé de la connexion au broker MQTT """
def __init__(self):
Thread.__init__(self)
def on_connect(self, client, userdata, flags, rc):
""" Abonnement aux topics """
affichage = "Connected to MQTT with result code " + str(rc)
print(affichage)
topic = Constantes.mqttTopic + '/cover/+/set'
client.subscribe(topic)
def on_message(self, client, userdata, msg):
""" Traitement du message recu """
url = "https://api.netatmo.com/api/setstate"
headers = {"Authorization": "Bearer " + iDiamant.access_token}
topic = str(msg.topic)
id_volet = topic.replace(Constantes.mqttTopic + '/cover/', '').replace('/set', '')
payload = str(msg.payload, encoding="utf-8")
position = -1
match payload:
case 'OPEN':
position = 100
case 'CLOSE':
position = 0
case _:
position = -1
data = {
"home": {
"id": iDiamant.volets[id_volet]['id_home'],
"modules": [
{
"id": id_volet,
"target_position": position,
"bridge": iDiamant.volets[id_volet]['bridge']
}
]
}
}
# Appel de l'API
response = requests.post(url, json=data, headers=headers)
if 200 != response.status_code:
print("Erreur : " + response.content)
def run(self):
""" Démarrage du service MQTT """
client = mqtt.Client()
if Constantes.mqttUser:
client.username_pw_set(Constantes.mqttUser, Constantes.mqttPassword)
client.on_connect = self.on_connect
client.on_message = self.on_message
client.connect(Constantes.mqttHost, Constantes.mqttPort, 60)
client.loop_forever()

View File

@@ -4,17 +4,17 @@
from const import Constantes
from idiamant import iDiamant
from idiamant2mqtt import iDiamant2Mqtt
from mqtt2idiamant import Mqtt2iDiamant
from time import sleep
iDiamant.getToken()
iDiamant.initDiscovery()
# Temps entre chaque pull >= 2
pullTime = Constantes.idiamantPullStatus
if pullTime < 2:
pullTime = 2
iDiamant.initMqtt()
# Envoie des ordres à iDiamant
# mqtt2idiamant = Mqtt2iDiamant()
# mqtt2idiamant.start()
mqtt2idiamant = Mqtt2iDiamant()
mqtt2idiamant.start()
# Refresh du token
while True:
sleep(iDiamant.expire_token / 2)
iDiamant.updateToken()