Initial commit
This commit is contained in:
commit
4519d1ece8
5 changed files with 205 additions and 0 deletions
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
*.bak
|
||||
unstable-*
|
||||
*.log
|
||||
.env
|
34
README.md
Normal file
34
README.md
Normal file
|
@ -0,0 +1,34 @@
|
|||
# Bluetooth LE to MQTT bridge for the Xiaomi Mijia Temperature & Humidity sensor
|
||||
|
||||
## Create environment file
|
||||
|
||||
Copy the `env_file` to `.env` and open it. Complete all environment variables:
|
||||
|
||||
- `MQTT_[SERVER/PORT/USER/PASSWORD/CLIENT_ID]` are used to connect with the broker.
|
||||
- `MQTT_TOPIC_PREFIX` define the prefix for all topics with sensor info.
|
||||
- `MQTT_TELE_PREFIX` is used to publish the sensor data, like battery or status.
|
||||
- `MQTT_SENSOR_NAME` contains the sensor name, useful to split the telemetry data if you have more than one sensors.
|
||||
- `MQTT_PUBLISH_DELAY` specify, in seconds, how many time should wait since the script take the measurements to publish in the broker
|
||||
- `MIJIA_BTLE_ADDRESS` constant with the BLE address of your Mijia device.. This can be retrieved activating the pairing mode in the sensor and scanning the BT devices
|
||||
|
||||
## Install dependencies
|
||||
|
||||
You'll need to install bluez and python3. Then you'll need pip3 to install bluepy.
|
||||
|
||||
Example on a Raspberry Pi 3:
|
||||
```sh
|
||||
$ sudo apt-get install python-pip libglib2.0-dev
|
||||
$ sudo pip3 install -r requirements.txt
|
||||
```
|
||||
|
||||
## Run
|
||||
|
||||
You can execute the script directly using the command:
|
||||
```sh
|
||||
$ ./main.py
|
||||
```
|
||||
|
||||
Or you can add a new entry in the `crontab`, like:
|
||||
```sh
|
||||
*/20 * * * * /usr/bin/python3 ~/scripts/mijia-temperature/main.py >~/scripts/mijia-temperature/last.log 2>&1
|
||||
```
|
22
env_file
Normal file
22
env_file
Normal file
|
@ -0,0 +1,22 @@
|
|||
# Broker configuration
|
||||
MQTT_SERVER=
|
||||
MQTT_PORT=
|
||||
MQTT_USER=
|
||||
MQTT_PASSWORD=
|
||||
MQTT_CLIENT_ID=
|
||||
|
||||
# Topic configuration
|
||||
MQTT_TOPIC_PREFIX=home/sensor
|
||||
MQTT_TELE_PREFIX=home/tele
|
||||
|
||||
MQTT_SENSOR_NAME=mijia-salon
|
||||
|
||||
MQTT_TOPIC_HUMIDITY=${MQTT_TOPIC_PREFIX}/humedad
|
||||
MQTT_TOPIC_TEMPERATURE=${MQTT_TOPIC_PREFIX}/temperatura
|
||||
MQTT_TOPIC_BATTERY=${MQTT_TELE_PREFIX}/${MQTT_SENSOR_NAME}/bateria
|
||||
MQTT_TOPIC_STATE=${MQTT_TELE_PREFIX}/${MQTT_SENSOR_NAME}/event
|
||||
|
||||
MQTT_PUBLISH_DELAY=5
|
||||
|
||||
# Sensor configuration
|
||||
MIJIA_BTLE_ADDRESS=
|
142
main.py
Executable file
142
main.py
Executable file
|
@ -0,0 +1,142 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
"""MiJia GATT to MQTT"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
|
||||
from dotenv import load_dotenv
|
||||
import paho.mqtt.client as mqtt
|
||||
from bluepy import btle
|
||||
|
||||
load_dotenv() # Cargamos las variables de entorno necesarias
|
||||
|
||||
MQTT_TOPIC_HUMIDITY = os.getenv('MQTT_TOPIC_HUMIDITY')
|
||||
MQTT_TOPIC_TEMPERATURE = os.getenv('MQTT_TOPIC_TEMPERATURE')
|
||||
MQTT_TOPIC_BATTERY = os.getenv('MQTT_TOPIC_BATTERY')
|
||||
MQTT_TOPIC_STATE = os.getenv('MQTT_TOPIC_STATE')
|
||||
|
||||
MQTT_PUBLISH_DELAY = int(os.getenv('MQTT_PUBLISH_DELAY'))
|
||||
MQTT_CLIENT_ID = os.getenv('MQTT_CLIENT_ID')
|
||||
|
||||
MQTT_SERVER = os.getenv('MQTT_SERVER')
|
||||
MQTT_PORT = int(os.getenv('MQTT_PORT'))
|
||||
MQTT_USER = os.getenv('MQTT_USER')
|
||||
MQTT_PASSWORD = os.getenv('MQTT_PASSWORD')
|
||||
|
||||
MIJIA_BTLE_ADDRESS = os.getenv('MIJIA_BTLE_ADDRESS')
|
||||
|
||||
MIJIA_BATTERY_SERVICE_UUID = btle.UUID('180f')
|
||||
MIJIA_BATTERY_CHARACTERISTIC_UUID = btle.UUID('2a19')
|
||||
|
||||
MIJIA_DATA_SERVICE_UUID = btle.UUID('226c0000-6476-4566-7562-66734470666d')
|
||||
MIJIA_DATA_CHARACTERISTIC_UUID = btle.UUID('226caa55-6476-4566-7562-66734470666d')
|
||||
MIJIA_DATA_CHARACTERISTIC_HANDLE = 0x0010
|
||||
|
||||
BTLE_SUBSCRIBE_VALUE = bytes([0x01, 0x00])
|
||||
BTLE_UNSUBSCRIBE_VALUE = bytes([0x00, 0x00])
|
||||
|
||||
battery = None
|
||||
temperature = None
|
||||
humidity = None
|
||||
|
||||
|
||||
def on_connect(client, userdata, flags, rc):
|
||||
client.publish(MQTT_TOPIC_STATE, 'connected', 1, True)
|
||||
|
||||
|
||||
class MyDelegate(btle.DefaultDelegate):
|
||||
def __init__(self):
|
||||
btle.DefaultDelegate.__init__(self)
|
||||
|
||||
def handleNotification(self, cHandle, data):
|
||||
fetch_sensor_data(bytearray(data).decode('utf-8'))
|
||||
|
||||
|
||||
def main():
|
||||
mqttc = mqtt.Client(MQTT_CLIENT_ID)
|
||||
mqttc.username_pw_set(MQTT_USER, MQTT_PASSWORD)
|
||||
mqttc.will_set(MQTT_TOPIC_STATE, 'disconnected', 1, True)
|
||||
mqttc.on_connect = on_connect
|
||||
|
||||
mqttc.connect(MQTT_SERVER, MQTT_PORT, 60)
|
||||
mqttc.loop_start()
|
||||
|
||||
last_msg_time = time.time()
|
||||
|
||||
while True:
|
||||
try:
|
||||
print('Connecting to ' + MIJIA_BTLE_ADDRESS)
|
||||
dev = btle.Peripheral(MIJIA_BTLE_ADDRESS)
|
||||
print('Set delegate')
|
||||
dev.setDelegate(MyDelegate())
|
||||
|
||||
# Get battery level
|
||||
if battery is None:
|
||||
fetch_battery_level(dev)
|
||||
print('Battery level: ' + str(battery))
|
||||
|
||||
# Subscribe to data characteristic
|
||||
if temperature is None or humidity is None:
|
||||
dev.writeCharacteristic(MIJIA_DATA_CHARACTERISTIC_HANDLE, BTLE_SUBSCRIBE_VALUE, True)
|
||||
while True:
|
||||
if dev.waitForNotifications(1.0):
|
||||
print('Temperature: ' + temperature)
|
||||
print('Humidity: ' + humidity)
|
||||
dev.writeCharacteristic(MIJIA_DATA_CHARACTERISTIC_HANDLE, BTLE_UNSUBSCRIBE_VALUE, True)
|
||||
dev.disconnect()
|
||||
break
|
||||
|
||||
if battery is not None and temperature is not None and humidity is not None:
|
||||
delay_gap = time.time() - last_msg_time
|
||||
if delay_gap < MQTT_PUBLISH_DELAY:
|
||||
time.sleep(MQTT_PUBLISH_DELAY - delay_gap)
|
||||
|
||||
publish_sensor_data(mqttc)
|
||||
last_msg_time = time.time()
|
||||
reset_variables()
|
||||
break
|
||||
|
||||
except (btle.BTLEDisconnectError, IOError):
|
||||
print("Disconnected :(")
|
||||
|
||||
def reset_variables():
|
||||
global battery
|
||||
global temperature
|
||||
global humidity
|
||||
|
||||
battery = None
|
||||
temperature = None
|
||||
humidity = None
|
||||
|
||||
|
||||
def fetch_battery_level(dev):
|
||||
global battery
|
||||
|
||||
battery_service = dev.getServiceByUUID(MIJIA_BATTERY_SERVICE_UUID)
|
||||
battery_characteristic = battery_service.getCharacteristics(MIJIA_BATTERY_CHARACTERISTIC_UUID)[0]
|
||||
battery = ord(battery_characteristic.read())
|
||||
|
||||
|
||||
def fetch_sensor_data(temp_hum):
|
||||
global temperature
|
||||
global humidity
|
||||
|
||||
pattern = re.compile('T=([\d.-]+) H=([\d.-]+)')
|
||||
match = re.match(pattern, temp_hum)
|
||||
if match:
|
||||
temperature = match.group(1)
|
||||
humidity = match.group(2)
|
||||
|
||||
|
||||
def publish_sensor_data(mqttc):
|
||||
mqttc.publish(MQTT_TOPIC_TEMPERATURE, temperature, 1, True)
|
||||
mqttc.publish(MQTT_TOPIC_HUMIDITY, humidity, 1, True)
|
||||
mqttc.publish(MQTT_TOPIC_BATTERY, battery, 1, True)
|
||||
time.sleep(MQTT_PUBLISH_DELAY)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print('Starting MiJia GATT client')
|
||||
main()
|
3
requirements.txt
Normal file
3
requirements.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
bluepy==1.3.0
|
||||
paho-mqtt==1.6.1
|
||||
python-dotenv==0.20.0
|
Loading…
Reference in a new issue