[Python] Capteur de température – humidité LCD Xiaomi BLUETOOTH

Vous avez créé un script LUA dont vous êtes fier, un .sh génial, un programme Python hors du commun, un Tuto, c'est ici que vous pouvez les partager.
PBC43
Messages : 4
Inscription : 22 nov. 2020, 11:04

[Python] Capteur de température – humidité LCD Xiaomi BLUETOOTH

Message par PBC43 »

Bonjour à tous :)

Lors d'une promotion, j'ai compulsivement acheté 2 exemplaires de ce capteur de température & humidité XIAOMI, celui qui ressemble à ça :
Image

Je pensais benoitement pouvoir les intégrer à mon installation domoticz, mais j'ai rapidement déchanté et (finalement) compris que le protocole utilisé est le Bluetooth... Mon serveur refusant obstinément de les reconnaitre, j'ai rapidement compris qu'il me fallait pour la nième fois réinventer l'eau chaude ^^

Après avoir découvert le protocole BLE, j'ai testé les différents framework qu'on peut trouver, et après bien des déconvenues mon choix s'est arrêté sur bleak : https://bleak.readthedocs.io/en/latest/index.html

Ma solution est, sans surprise, de créer un capteur virtuel de température & humidité et qu'un script se charge de le mettre à jour avec les valeurs qu'il lit sur le périphérique.

Voici le fruit de mon travail :

Code : Tout sélectionner

"""
Domoticz dummy device updater for XIAOMI bluetooth Temp / Hum LCD sensor
----------------

A script getting data from this model of sensor and updating a related domoticz dummy device.

Created on 2020-11-22 by PBC43 <oratoire43+dev@gmail.com>
Based on https://github.com/hbldh/bleak/blob/master/examples/service_explorer.py

"""
import platform
import asyncio
import logging
import urllib.request
import base64
import time

from bleak import BleakClient

DEBUG = False
MAXRETRY = 15
RETRYSLEEP = 10
UPDATEOK = "success"
UPDATEFAILED = "FAILED"

DOMSERVER   = "http://localhost:8080"
DOMUSER = "putyourdomoticzusernamehere" # !!!
DOMPASS = "putyourdomoticzuserpasswordhere" # !!!

BASE64STR = base64.encodebytes(('%s:%s' % (DOMUSER, DOMPASS)).encode()).decode().replace('\n', '')

TARGETSERVICE = "226c0000-6476-4566-7562-66734470666d"
TARGETCHARACTERISTIC = "226caa55-6476-4566-7562-66734470666d"

ITEMS = [
          {"alias": "Salon", "domid": "putyourdomoticzdeviceidhere", "device": BleakClient("putyourbledeviceaddresshere")}, # !!!
          #{"alias": "Chambre", "domid": "234", "device": BleakClient("58:2D:34:3A:1B:3A")}, # for example
        ]
        
FAIL = "\033[91m"
ENDC = "\033[0m"
        
def domrequest (url):
  request = urllib.request.Request(url)
  request.add_header("Authorization", "Basic %s" % BASE64STR)
  response = urllib.request.urlopen(request)
  return response.read()
    
class receivedvalues:
  def _decode(self, val):
    try:
      return(val.decode())
    except:
      return(val)
  
  def __init__(self):
    self.temperature = None
    self.humidity = None
    self.comfort = None
  
  def put(self, data):  
    data = self._decode(data)

    if "=" in data:
      _values = data.split(" ")
      
      self.temperature = float(_values[0].split("=")[1])
      self.humidity = float(_values[1].rstrip("\x00").split("=")[1])
      
      if self.humidity < 40:
          self.comfort = "2"
      elif self.humidity <= 60:
          self.comfort = "1"
      elif self.humidity > 60:
          self.comfort = "3"       
          
def gettempval(sender, data, receiver):
  receiver.put(data)

async def run(item):
    global UPDATEOK, UPDATEFAILED
    
    alias = item["alias"]
    domid = item["domid"]
    device = item["device"]
    
    async with device:
        
        x = await device.is_connected()
        print(alias, "connected")
        
        service = list(filter(lambda service: service.uuid == TARGETSERVICE, device.services))[0]
        characteristic = list(filter(lambda characteristic: characteristic.uuid == TARGETCHARACTERISTIC, service.characteristics))[0]

        try:
          values = receivedvalues()                 
          await device.start_notify(characteristic.uuid, lambda *args: gettempval(*args, values))
          await asyncio.sleep(5.0)
          
          print(alias, "=>", "temperature =", values.temperature, "humidity =", values.humidity, "comfort =", values.comfort)
          
          if(float(values.temperature) and float(values.humidity) and float(values.comfort)):
            req = domrequest(
                              DOMSERVER + "/json.htm?type=command&param=udevice&idx=" + domid + "&nvalue=0&svalue=" 
                              + str(values.temperature) + ";" 
                              + str(values.humidity) + ";"
                              + str(values.comfort)
                            )
            """
            if "OK" in str(req):
              res = "OK"
            else:
              res = "FAILED"
            """
              
            print(alias, ": domoticz update =>", (UPDATEOK if "OK" in str(req) else UPDATEFAILED)) 
            
          await device.disconnect()
                               
        except Exception as e:
          print(e)
                  
            
          

index = 0
        
async def connect_to_device(item, loop):
    global index
    global MAXRETRY
    global RETRYSLEEP
    
    alias = item["alias"]
    initialsleep = index * 5
    
    time.sleep(initialsleep)
    print("starting", alias, "loop")
    ok = False
    retry = 0
    index = index + 1
        
    while retry < MAXRETRY and not ok:
      print("> connect to", alias, retry + 1)
      try:
        await run(item)
        ok = True
        print("<", alias, "disconnected")

      except Exception as e:
        print(alias, "conn. failed", FAIL, (str(e) if DEBUG else ""), ENDC)

        if(retry + 1 < MAXRETRY):
          time.sleep(RETRYSLEEP)

      retry = retry + 1

def lookup(items):

    loop = asyncio.get_event_loop()
    i = 0
    tasks = asyncio.gather(*(connect_to_device(item, loop) for item in ITEMS))

    loop.run_until_complete(tasks)
                      
if __name__ == "__main__":
    lookup(ITEMS)
NB :
  • Je débute en python, et à ce titre je n'utilise pas encore les solutions les plus simples ;)
  • L'établissement de la liaison est très erratique, soit que le périphérique n'est à cet instant pas visible, soit que la connexion est interrompue pour une raison inconnue. C'est pourquoi j'ai fixé un nombre important d'essais et que je ne lance personnellement mon script que toutes les 15 minutes via crontab
  • A réinventer l'eau chaude, quelqu'un finit toujours par vous annoncer qu'un autre l'a fait avant vous, et que votre solution n'est pas propre, pour x raisons. Je ne suis pas revanchard, donc n'hésitez pas à commenter ! :)
cyril21
Messages : 10
Inscription : 25 avr. 2021, 08:46
Localisation : 28
Contact :

Re: [Python] Capteur de température – humidité LCD Xiaomi BLUETOOTH

Message par cyril21 »

Bonjour,

Je ne vois pas à quel modèle ton capteur correspond. Sache qu'il existe des firmware custom Xiaomi, très facile à installer pour ce type de capteurs. Vas voir sur : https://github.com/pvvx/ATC_MiThermometer
Pour ma part j'ai des modèles LYWSD03MMC qui fonctionnent très bien avec le nouveau firmware.

J'ai également écris un script pour Domoticz en python. Il utilise par contre la librairie bluepy:

Code : Tout sélectionner

import DomoticzEvents as DE
import sys
sys.path.append("/usr/local/lib/python3.7/dist-packages/")
from bluepy.btle import Scanner, DefaultDelegate
import os

midevices = {
    "a4:c1:38:xx:xx:xx": {'device': 9}, 
    "a4:c1:38:xx:xx:xx": {'device': 10}, 
    "a4:c1:38:xx:xx:xx": {'device': 11}  
}

otherdevices = {}

def update(idx, t, h):
    #0=Normal 1=Comfortable 2=Dry 3=Wet
    c = 0
    url = "http://127.0.0.1:8080/json.htm?type=command&param=udevice&idx="+str(idx)+"&nvalue=0&svalue="+str(t)+';'+str(h)+';'+str(c)
    os.system('/usr/bin/curl "'+url+'"')

def log(s):
    #DE.Log(str(s))
    return

def display(t):
    for a,b in t.items():
        log(a)
        for c,d in b.items():
            log(str(c)+' : '+str(d))

# 1a18 a4c138a3c592 00cc 27 4e    0b58 3c
#  -      mac       temp hu %bat  vbat frame_packet_counter

class ScanDelegate(DefaultDelegate):
    def __init__(self):
        DefaultDelegate.__init__(self)

    def handleDiscovery(self, dev, isNewDev, isNewData):
        global midevices, scan, otherdevices
        t = {}
        if dev.addr in midevices:
            for (adtype, desc, value) in dev.getScanData():
                t[adtype] = value
            if t[22][0:4] == '1a18' and len(t[22]) == 30:
                if not 'temp' in midevices[dev.addr]:
                    scan = scan + 1
                data = t[22]
                #midevices[dev.addr]['name'] = t[9]
                midevices[dev.addr]['temp'] = int(data[16:20],16)/10
                midevices[dev.addr]['humi'] = int(data[20:22], 16)
                midevices[dev.addr]['bat'] = int(data[22:24], 16)
                midevices[dev.addr]['vbat'] = int(data[24:28], 16) /1000
                midevices[dev.addr]['count'] = int(data[28:30], 16)
                #log(dev.addr+' == '+data+' > '+str(midevices[dev.addr]['bat'])+'%')
        else:
            if isNewDev:
                otherdevices[dev.addr] = {}
            for (adtype, desc, value) in dev.getScanData():
                otherdevices[dev.addr][adtype] = value

#========= MAIN =========#

log("BLUETOOTH START")

scanner = Scanner().withDelegate(ScanDelegate())

scanner.clear()
scanner.start()

i = 0
scan = 0
while scan<len(midevices) and i < 5:
    i = i + 1
    scanner.process(2)
    
scanner.stop()

log('BLUETOOTH END '+str(i))

#display(midevices)

for m, d in midevices.items():
    if 'temp' in d and d['device']:
        update(d['device'], str(d['temp']), str(d['humi']))
        
#display(otherdevices)
    
log('BLUETOOTH UPDATE ')


PBC43
Messages : 4
Inscription : 22 nov. 2020, 11:04

Re: [Python] Capteur de température – humidité LCD Xiaomi BLUETOOTH

Message par PBC43 »

Bonjour Cyril,

Merci de m'avoir permis de sortir de ce cauchemar ^^ Je n'ai pas eu besoin de flasher mes capteurs, j'ai juste un peu adapté ton script en galérant pas mal pour décoder la trame et en extraire les deux seules informations qui m'intéressent, la température et l'humidité.

Voici :

Code : Tout sélectionner

            if t[22][0:4] == '95fe' and len(t[22]) == 40:
                if not 'temp' in midevices[dev.addr]:
                    scan = scan + 1
                data = t[22]
                midevices[dev.addr]['temp'] = int(data[32:34],16) / 10
                midevices[dev.addr]['humi'] = int(data[38:40] + data[36:38], 16) / 10
Tout roule depuis hier, merci encore pour ton partage. J'en ai profité pour commander un jeu de 3 LYWSD03MMC pour compléter mon installation.
kisskool
Messages : 70
Inscription : 01 juin 2021, 11:24

Re: [Python] Capteur de température – humidité LCD Xiaomi BLUETOOTH

Message par kisskool »

cyril21 a écrit : 02 mai 2021, 23:40 Bonjour,

Je ne vois pas à quel modèle ton capteur correspond. Sache qu'il existe des firmware custom Xiaomi, très facile à installer pour ce type de capteurs. Vas voir sur : https://github.com/pvvx/ATC_MiThermometer
Pour ma part j'ai des modèles LYWSD03MMC qui fonctionnent très bien avec le nouveau firmware.
Bonjour Cyril,je cherche à m'equiper de capteur de temp et les LYWSD03MMC sont vraiment pas chère pour équiper toute la maison ! Si j'ai bien compris il suffit de flasher les capteurs et on peut les utiliser avec domoticz sans la passerelle xiaomi ?
Il faut un module bluetooh pour la communication c'est tout ?
Ay97iyN0gP.png
Ay97iyN0gP.png (146.95 Kio) Consulté 5696 fois
kisskool
Messages : 70
Inscription : 01 juin 2021, 11:24

Re: [Python] Capteur de température – humidité LCD Xiaomi BLUETOOTH

Message par kisskool »

Personne ?
cyril21
Messages : 10
Inscription : 25 avr. 2021, 08:46
Localisation : 28
Contact :

Re: [Python] Capteur de température – humidité LCD Xiaomi BLUETOOTH

Message par cyril21 »

Bonjour,

Oui ces modules son flashables très facilement grâce au site que j’ai cité plus haut.
Ce qui est intéressant avec le nouveau firmware proposé est qu’il permet d’émettre en utilisant plusieurs protocoles dont celui de xiaomi pour garder la compatibilité. Il a aussi d’autres avantages à découvrir sur le site.

Avec un récepteur bluetooth 4 de bonne qualité et un script tu pourras les exploiter facilement.
Le script que j ai proposé est prévu pour être inclus directement dans l éditeur. Cela fonctionne mais ce n’est pas stable et cela fait planter domoticz au bout d’un certain temps.
Il y a probablement des scripts sur ce forum plus propre pour cet usage. N’hésite pas à proposer tes trouvailles. Bon courage !
kisskool
Messages : 70
Inscription : 01 juin 2021, 11:24

Re: [Python] Capteur de température – humidité LCD Xiaomi BLUETOOTH

Message par kisskool »

Merci pour ta réponse. Tu as un conseil pour le recepteur ? Un modèle testé ?
Dernière modification par Keros le 12 juin 2021, 21:56, modifié 1 fois.
Raison : Merci de ne pas citer le message précédent lors d'une réponse directe
cyril21
Messages : 10
Inscription : 25 avr. 2021, 08:46
Localisation : 28
Contact :

Re: [Python] Capteur de température – humidité LCD Xiaomi BLUETOOTH

Message par cyril21 »

J’utilise le dongle usb UD100-G03 auquel j’ai changé l’antenne pour une meilleure portée.
C’est probablement un des meilleurs récepteurs BT longue portée. Très utile en effet dans une grande maison et avec des devices BT en extérieur par exemple.
kisskool
Messages : 70
Inscription : 01 juin 2021, 11:24

Re: [Python] Capteur de température – humidité LCD Xiaomi BLUETOOTH

Message par kisskool »

J'ai reçu et flasher mes sondes, par contre maintenant j'avoue que je suis un peu perdu !

J'ai suivi ce tuto :https://computingforgeeks.com/connect-t ... -terminal/
pour faire fonctionner le bluetooth
Je pense que c'est ok de ce côté mais déjà le script me donne ça dans les logs :

Code : Tout sélectionner

2021-06-23 23:55:47.332 Error: EventSystem: Failed to execute python event script "xiaomi_blt"
2021-06-23 23:55:47.332 Error: EventSystem: Traceback (most recent call last):
2021-06-23 23:55:47.332 Error: EventSystem: File "<string>", line 69, in <module>
2021-06-23 23:55:47.332 Error: EventSystem: File "/usr/local/lib/python3.7/dist-packages/bluepy/btle.py", line 797, in start
2021-06-23 23:55:47.332 Error: EventSystem: self._mgmtCmd(self._cmd()+"end")
2021-06-23 23:55:47.332 Error: EventSystem: File "/usr/local/lib/python3.7/dist-packages/bluepy/btle.py", line 312, in _mgmtCmd
2021-06-23 23:55:47.332 Error: EventSystem: raise BTLEManagementError("Failed to execute management command '%s'" % (cmd), rsp)
2021-06-23 23:55:47.332 Error: EventSystem: bluepy.btle.BTLEManagementError: Failed to execute management command 'scanend' (code: 11, error: Rejected) 
J'ai installé bluepy mais après je ne sais pas quoi faire ?

Pouvez-vous m'aider?.
cyril21
Messages : 10
Inscription : 25 avr. 2021, 08:46
Localisation : 28
Contact :

Re: [Python] Capteur de température – humidité LCD Xiaomi BLUETOOTH

Message par cyril21 »

Il faut plus d'informations.
Peux-tu fournir le script que tu utilises ?
Tu peux également tester cet exemple issue de la doc bluepy:

Code : Tout sélectionner

from bluepy.btle import Scanner, DefaultDelegate

class ScanDelegate(DefaultDelegate):
    def __init__(self):
        DefaultDelegate.__init__(self)

    def handleDiscovery(self, dev, isNewDev, isNewData):
        if isNewDev:
            print "Discovered device", dev.addr
        elif isNewData:
            print "Received new data from", dev.addr

scanner = Scanner().withDelegate(ScanDelegate())
devices = scanner.scan(10.0)

for dev in devices:
    print "Device %s (%s), RSSI=%d dB" % (dev.addr, dev.addrType, dev.rssi)
    for (adtype, desc, value) in dev.getScanData():
        print "  %s = %s" % (desc, value)
Répondre