First Files
This commit is contained in:
parent
6c17a5b291
commit
ee9a73ff70
4 changed files with 179 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
## Configurations should be local
|
||||||
|
*.conf
|
55
README.md
Normal file
55
README.md
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
# POC: Xiaomi Airpurifier 3H MQTT Bridge
|
||||||
|
This is a proof-of-concept, lacking a lot of errorhandling.
|
||||||
|
|
||||||
|
The Xiaomi Airpurifier 3H uses the (new) miot api.
|
||||||
|
This script bridges the air pufifier to the following state and control topics in mqtt.
|
||||||
|
|
||||||
|
These topics are reported from the device:
|
||||||
|
|
||||||
|
| STATE Topic | Values to expect | Meaning |
|
||||||
|
|-------------------------------------|------------------------------|----------------------------------------------------------------------------|
|
||||||
|
| $device/STATE/TIMESTAMP | 2020-03-29T15:44 | Timestamp of the last state update |
|
||||||
|
| $device/STATE/airQualityIndex | 0-999 | AQI current value (ppm2.5?) |
|
||||||
|
| $device/STATE/airQualityIndexAvg | 0-999 | AQI as displayed on the frontpanel (?) |
|
||||||
|
| $device/STATEairTemperatureC | FLOAT | Current temperature in °C |
|
||||||
|
| $device/STATE/airRelHumidityPercent | 0-100 | Relative humidity in % |
|
||||||
|
| $device/STATE/fanMotorSpeed | INT | Current motorspeed in rpm |
|
||||||
|
| $device/STATE/fanLevel | 1-3 | Fanlevel preset as selected by the button (1,2,3 waves) |
|
||||||
|
| $device/STATE/fanFavoriteSetLevel | 1-14 | Fanlevel preset for heart-mode (fanLevels 1-3 are positions in this range) |
|
||||||
|
| $device/STATE/filterUsedHours | INT | Filter-usage-time in hours |
|
||||||
|
| $device/STATE/filterRemainingPercent| 0-100 | Remaining filter-live in % |
|
||||||
|
| $device/STATE/filterRfidProductId | 0:0:31:31 | Filter product-id |
|
||||||
|
| $device/STATE/filterType | Regular, ... | Common name for the filtertype |
|
||||||
|
| $device/STATE/filterRfidTag | 80:66:58:da:7f:55:4 | The filters unique ID |
|
||||||
|
| $device/STATE/deviceBuzzerEnabled | True/False | Can the buzzer buzz? |
|
||||||
|
| $device/STATE/deviceLedBrightnes | Bright, Dim, Off | Brightness... |
|
||||||
|
| $device/STATE/deviceChildLockActive | True/False | Is the childlock active? |
|
||||||
|
| $device/STATE/devicePowerOn | True/False | Is the device powered on? |
|
||||||
|
| $device/STATE/devicePower | on/off | Power... |
|
||||||
|
| $device/STATE/deviceMode | Auto, Silent, Favorite, Fan | Mode as selcted by the front-button. (3 fanmodes indicated by fanLevel) |
|
||||||
|
| $device/STATE/statPurifiedVolumeM3 | INT | Purified volume of air |
|
||||||
|
| $device/STATE/statTimeActive | INT | Seconds active |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
These topics can control the device:
|
||||||
|
|
||||||
|
| CMD Topic | Values expected | Meaning |
|
||||||
|
|----------------------|------------------|-------------------------------|
|
||||||
|
| $device/CMD/power | on/off | Turn the device on or off |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Prerequisits
|
||||||
|
- Python 3
|
||||||
|
- PAHO MQTT-Client module
|
||||||
|
- python-miio > 5.0.1 (available from PIP)
|
||||||
|
- The devices security token
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
- Copy airpurifier.conf.sample to airpurifier.conf
|
||||||
|
- Adjust the values in airpurifier.conf
|
||||||
|
- Run miotAirpurifierBridge.py
|
||||||
|
|
||||||
|
Set the environment `airpurifierConfigFile` to use a different configfile.
|
9
airpurifier.conf.sample
Normal file
9
airpurifier.conf.sample
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
mqtt_ip = "1.2.3.5" # MQTT Broker IP
|
||||||
|
mqtt_topic = "testing/airPurifierBridge" # MQTT Topic to use
|
||||||
|
|
||||||
|
miot_ip ="1.2.3.4" # Xiaomi Arpurfier 3H IP-Address
|
||||||
|
miot_token ="1234567890ABCDEFGHIJKLMNopqrstuv" # device Token
|
||||||
|
|
||||||
|
update_interval = (10 * 60) # Update between regular updates of the state (10 minutes)
|
||||||
|
|
||||||
|
loglevel = 1 # 0=Exceptions 1=Application 2=Actions 3=Debug
|
113
miotAirpurifierBridge.py
Normal file
113
miotAirpurifierBridge.py
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
from datetime import datetime
|
||||||
|
import paho.mqtt.client as paho
|
||||||
|
import miio
|
||||||
|
|
||||||
|
|
||||||
|
## Logging to con
|
||||||
|
def log(level, msg):
|
||||||
|
if ( level <= loglevel ):
|
||||||
|
now = datetime.now()
|
||||||
|
print(now.strftime("%Y-%m-%dT%H:%M:%S") + " " + str(level) + " " + msg)
|
||||||
|
|
||||||
|
|
||||||
|
## Receive the airpurifiers status and write it to mqtt
|
||||||
|
def updateMqttStateTopic():
|
||||||
|
log(3, "Starting update of state-topic.")
|
||||||
|
apStatus = ap.status()
|
||||||
|
now = datetime.now()
|
||||||
|
mqttClient.publish(mqtt_stateTopic + "TIMESTAMP", now.strftime("%Y-%m-%dT%H:%M:%S"))
|
||||||
|
mqttClient.publish(mqtt_stateTopic + "airQualityIndex", apStatus.aqi)
|
||||||
|
mqttClient.publish(mqtt_stateTopic + "airQualityIndexAvg", apStatus.average_aqi)
|
||||||
|
mqttClient.publish(mqtt_stateTopic + "airTemperatureC", apStatus.temperature)
|
||||||
|
mqttClient.publish(mqtt_stateTopic + "airRelHumidityPercent", apStatus.humidity)
|
||||||
|
mqttClient.publish(mqtt_stateTopic + "fanMotorSpeed", apStatus.motor_speed)
|
||||||
|
mqttClient.publish(mqtt_stateTopic + "fanLevel", apStatus.fan_level)
|
||||||
|
mqttClient.publish(mqtt_stateTopic + "fanFavoriteSetLevel", apStatus.favorite_level)
|
||||||
|
mqttClient.publish(mqtt_stateTopic + "filterUsedHours", apStatus.filter_hours_used)
|
||||||
|
mqttClient.publish(mqtt_stateTopic + "filterRemainingPercent", apStatus.filter_life_remaining)
|
||||||
|
mqttClient.publish(mqtt_stateTopic + "filterRfidProductId", apStatus.filter_rfid_product_id)
|
||||||
|
mqttClient.publish(mqtt_stateTopic + "filterRfidTag", apStatus.filter_rfid_tag)
|
||||||
|
mqttClient.publish(mqtt_stateTopic + "filterType", apStatus.filter_type.name)
|
||||||
|
mqttClient.publish(mqtt_stateTopic + "deviceBuzzerEnabled", apStatus.buzzer)
|
||||||
|
mqttClient.publish(mqtt_stateTopic + "deviceLedBrightnes", apStatus.led_brightness.name)
|
||||||
|
mqttClient.publish(mqtt_stateTopic + "deviceChildLockActive", apStatus.child_lock)
|
||||||
|
mqttClient.publish(mqtt_stateTopic + "devicePowerOn", apStatus.is_on)
|
||||||
|
mqttClient.publish(mqtt_stateTopic + "devicePower", apStatus.power)
|
||||||
|
mqttClient.publish(mqtt_stateTopic + "deviceMode", apStatus.mode.name)
|
||||||
|
mqttClient.publish(mqtt_stateTopic + "statPurifiedVolumeM3", apStatus.purify_volume)
|
||||||
|
mqttClient.publish(mqtt_stateTopic + "statTimeActive", apStatus.use_time)
|
||||||
|
# mqttClient.publish(mqtt_stateTopic + "deviceBuzzerSetVolume", apStatus.buzzer_volume)
|
||||||
|
# mqttClient.publish(mqtt_stateTopic + "deviceLedEnabled", apStatus.led)
|
||||||
|
log(3, "Update of state-topic finished.")
|
||||||
|
|
||||||
|
|
||||||
|
## MQTT broker disconnected
|
||||||
|
def on_mqttDisconnect(client, userdata, rc):
|
||||||
|
log(1, "MQTT broker disconnected")
|
||||||
|
|
||||||
|
|
||||||
|
## MQTT broker connected
|
||||||
|
def on_mqttConnect(client, userdata, flags, rc):
|
||||||
|
log(1, "MQTT broker connected")
|
||||||
|
|
||||||
|
|
||||||
|
## MQTT Subscription Loop Callback Function
|
||||||
|
def on_mqttMessage(client, userdata, message):
|
||||||
|
mqttMsgData = message.payload.decode("utf-8")
|
||||||
|
mqttMsgTopic = message.topic
|
||||||
|
log(2, "RECEIVED: " + mqttMsgTopic + ":" + mqttMsgData)
|
||||||
|
if ( mqttMsgTopic == mqtt_cmdTopic+"power" ):
|
||||||
|
if (mqttMsgData == "on"):
|
||||||
|
ap.on()
|
||||||
|
log(2, "ACTION: Power On")
|
||||||
|
elif (mqttMsgData == "off"):
|
||||||
|
ap.off()
|
||||||
|
log(2, "ACTION: Power Off")
|
||||||
|
## Force update of STATE after switching things
|
||||||
|
time.sleep(5)
|
||||||
|
updateMqttStateTopic()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## --------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Get confiuration
|
||||||
|
configfile = os.getenv('airpurifierConfigFile', 'airpurifier.conf')
|
||||||
|
exec(open(configfile).read())
|
||||||
|
|
||||||
|
|
||||||
|
log(1, "---------------- Starting Air Purifier Bridge ----------------")
|
||||||
|
log(1, "Loaded confguration file: " + configfile)
|
||||||
|
|
||||||
|
|
||||||
|
## The airpurifier object
|
||||||
|
ap = miio.airpurifier_miot.AirPurifierMiot(ip=miot_ip, token=miot_token)
|
||||||
|
|
||||||
|
|
||||||
|
## The MQTT Client object
|
||||||
|
mqttClient = paho.Client("airPurifierBridge")
|
||||||
|
log(1, "Connecting to MQTT broker " + mqtt_ip)
|
||||||
|
mqttClient.connect(mqtt_ip)
|
||||||
|
time.sleep(5)
|
||||||
|
|
||||||
|
|
||||||
|
## Subscribe to mqtt command topics and start the loop
|
||||||
|
mqtt_cmdTopic = mqtt_topic + "/CMD/"
|
||||||
|
mqttClient.subscribe(mqtt_cmdTopic+"power")
|
||||||
|
mqttClient.on_message = on_mqttMessage
|
||||||
|
mqttClient.on_connect = on_mqttConnect
|
||||||
|
mqttClient.on_disconnect = on_mqttDisconnect
|
||||||
|
log(1, "Starting the subscription loop on " + mqtt_cmdTopic)
|
||||||
|
mqttClient.loop_start()
|
||||||
|
|
||||||
|
|
||||||
|
## State Update Loop
|
||||||
|
mqtt_stateTopic = mqtt_topic + "/STATE/"
|
||||||
|
log(1, "Starting the state update loop on " + mqtt_stateTopic)
|
||||||
|
while True:
|
||||||
|
updateMqttStateTopic()
|
||||||
|
time.sleep(update_interval)
|
Reference in a new issue