├── LICENSE ├── README.md ├── requirements.txt └── rollshade.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 ccmtozzi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Zemismart Roller Shade Integration 2 | This is a Python script to connect a Raspberry PI to a Zemismart Roller Shade. It listens to an MQTT topic and executes a close or open command based on that topic. 3 | 4 | # Requirements 5 | 6 | - Python 3+ 7 | - pip (to automatically install python dependencies) 8 | 9 | # Dependencies 10 | - paho-mqtt 11 | - bluepy 12 | - [Zemismart](https://github.com/GylleTanken/python-zemismart-roller-shade) 13 | - libglib2.0-dev ```sudo apt-get install libglib2.0-dev``` 14 | 15 | # Install 16 | 17 | 1. Download or clone the repository: 18 | 2. In the directory run ```pip install -r requirements.txt``` 19 | 3. Configure details in ```rollshade.py``` and then run with ```python rollshade.py``` 20 | 21 | # Home Assistant Config 22 | 23 | Just add a MQTT cover: 24 | 25 | ```yaml 26 | ... 27 | cover: 28 | - platform: mqtt 29 | name: "Blinds Bedroom" 30 | state_topic: "blinds/00:00:00:00:00:00/status" 31 | command_topic: "blinds/00:00:00:00:00:00" 32 | qos: 0 33 | state_open: "on" 34 | state_closed: "off" 35 | payload_open: "open" 36 | payload_close: "close" 37 | retain: false 38 | optimistic: false 39 | ``` 40 | 41 | # What Next? 42 | 43 | - Get information from the Shade (battery, state, etc..), currently it's just sending command, for open and close. 44 | - ESP32 version - Would prefer to run this in a ESP32 instead of a Raspberry PI 45 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | paho-mqtt 2 | bluepy 3 | git+git://github.com/GylleTanken/python-zemismart-roller-shade.git#egg=Zemismart -------------------------------------------------------------------------------- /rollshade.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import paho.mqtt.client as mqtt 3 | import time 4 | import re 5 | import Zemismart 6 | 7 | ### Variables 8 | 9 | mqtt_client = "192.168.1.1" #mqtt IP 10 | mqtt_port = 1883 11 | mqtt_user = "username" 12 | mqtt_password = "password" 13 | mqtt_path = "blinds" 14 | dev_pin = 8888 15 | 16 | #### Don't edit below here 17 | 18 | # btle.Debugging=True 19 | 20 | 21 | def shade_command(fble, fcmd, fpos=100): 22 | print ("["+ fble + "] Connecting") 23 | shade = Zemismart.Zemismart(fble, dev_pin) 24 | with shade: 25 | print ("["+ fble + "] Connected!") 26 | if fcmd == "open": 27 | shade.open() 28 | elif fcmd == "close": 29 | shade.close() 30 | elif fcmd == "stop": 31 | shade.stop() 32 | elif fcmd == "set_position": 33 | shade.set_position(int(fpos)) 34 | else: 35 | print("Unrecognized command.") 36 | print ("["+ fble + "] Disconnected") 37 | 38 | # The callback for when the client receives a CONNACK response from the server. 39 | def on_connect(client, userdata, flags, rc): 40 | print("Connected to MQTT with result code "+str(rc)) 41 | 42 | # Subscribing in on_connect() means that if we lose the connection and 43 | # reconnect then subscriptions will be renewed. 44 | client.subscribe(mqtt_path + "/#") 45 | 46 | def checkMAC(x): 47 | if re.match("[0-9a-f]{2}([-:])[0-9a-f]{2}(\\1[0-9a-f]{2}){4}$", x.lower()): 48 | return 1 49 | else: 50 | return 0 51 | 52 | # The callback for when a PUBLISH message is received from the server. 53 | def on_message(client, userdata, msg): 54 | # print ("message comming in") 55 | mac = msg.topic.replace(mqtt_path + "/", "") 56 | if msg.topic.find("set_position") > 0: 57 | mac = mac.replace("/set_position", "") 58 | if msg.topic.find("status") > 0: 59 | mac = mac.replace("/status", "") 60 | if msg.topic.find("/position") > 0: 61 | mac = mac.replace("/position", "") 62 | if checkMAC(mac) == 0: 63 | print ("["+ mac + "] Is not a valid Mac Address") 64 | return 65 | if msg.topic == (mqtt_path + "/" + mac + "/set_position"): 66 | t = 1 67 | while t <= 3: 68 | try: 69 | shade_command(mac, "set_position", msg.payload.decode()) 70 | client.publish(msg.topic.replace("/set_position", "") + "/position", msg.payload.decode(), qos=0, retain=False) 71 | print ("["+ mac + "] Status Published: " + msg.topic.replace("/set_position", "") + "/position") 72 | print ("["+ mac + "] Finished") 73 | break 74 | except: 75 | time.sleep(0.5) 76 | if t <= 3: 77 | print ("["+ mac + "] Error! - Trying to Connect Again! (" + str(t) + "/3)") 78 | else: 79 | print ("["+ mac + "] Error! - Can't Connect") 80 | t += 1 81 | if msg.payload.decode() == "open": 82 | t = 1 83 | while t <= 3: 84 | try: 85 | shade_command(mac, "open") 86 | client.publish(msg.topic + "/status", "on", qos=0, retain=False) 87 | print ("["+ mac + "] Status Published: " + msg.topic + "/status") 88 | print ("["+ mac + "] Finished") 89 | break 90 | except: 91 | time.sleep(0.5) 92 | if t <= 3: 93 | print ("["+ mac + "] Error! - Trying to Connect Again! (" + str(t) + "/3)") 94 | else: 95 | print ("["+ mac + "] Error! - Can't Connect") 96 | t += 1 97 | if msg.payload.decode() == "close": 98 | t = 1 99 | while t <= 3: 100 | try: 101 | shade_command(mac, "close") 102 | client.publish(msg.topic + "/status", "off", qos=0, retain=False) 103 | print ("["+ mac + "] Status Published: " + msg.topic + "/status") 104 | print ("["+ mac + "] Finished") 105 | break 106 | except: 107 | time.sleep(0.5) 108 | if t <= 3: 109 | print ("["+ mac + "] Error! - Trying to Connect Again! (" + str(t) + "/3)") 110 | else: 111 | print ("["+ mac + "] Error! - Can't Connect") 112 | t += 1 113 | if msg.payload.decode() == "stop": 114 | t = 1 115 | while t <= 3: 116 | try: 117 | shade_command(mac, "stop") 118 | client.publish(msg.topic + "/status", "on", qos=0, retain=False) 119 | print ("["+ mac + "] Status Published: " + msg.topic + "/status") 120 | print ("["+ mac + "] Finished") 121 | break 122 | except: 123 | time.sleep(0.5) 124 | if t <= 3: 125 | print ("["+ mac + "] Error! - Trying to Connect Again! (" + str(t) + "/3)") 126 | else: 127 | print ("["+ mac + "] Error! - Can't Connect") 128 | t += 1 129 | 130 | client = mqtt.Client() 131 | client.on_connect = on_connect 132 | client.on_message = on_message 133 | 134 | client.username_pw_set(mqtt_user, password=mqtt_password) 135 | client.connect(mqtt_client, mqtt_port, 60) 136 | 137 | # Blocking call that processes network traffic, dispatches callbacks and 138 | # handles reconnecting. 139 | # Other loop*() functions are available that give a threaded interface and a 140 | # manual interface. 141 | client.loop_forever() 142 | --------------------------------------------------------------------------------