From ee9a73ff703505a4d0f485dc1527fa44ae0bf971 Mon Sep 17 00:00:00 2001 From: Joerg Elfring Date: Wed, 1 Apr 2020 21:15:04 +0200 Subject: [PATCH] First Files --- .gitignore | 2 + README.md | 55 +++++++++++++++++++ airpurifier.conf.sample | 9 ++++ miotAirpurifierBridge.py | 113 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 179 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 airpurifier.conf.sample create mode 100644 miotAirpurifierBridge.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3975747 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +## Configurations should be local +*.conf diff --git a/README.md b/README.md new file mode 100644 index 0000000..07cdb00 --- /dev/null +++ b/README.md @@ -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. diff --git a/airpurifier.conf.sample b/airpurifier.conf.sample new file mode 100644 index 0000000..4bae939 --- /dev/null +++ b/airpurifier.conf.sample @@ -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 diff --git a/miotAirpurifierBridge.py b/miotAirpurifierBridge.py new file mode 100644 index 0000000..7681207 --- /dev/null +++ b/miotAirpurifierBridge.py @@ -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)