This commit is contained in:
Your Name
2017-01-11 16:24:43 +03:00
commit 1ae3af4c59
7 changed files with 344 additions and 0 deletions
+93
View File
@@ -0,0 +1,93 @@
#docker-compose file, because it contains passwords
docker-compose.yaml
docker-compose.yml
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
.hypothesis/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# IPython Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# dotenv
.env
# virtualenv
venv/
ENV/
# Spyder project settings
.spyderproject
# Rope project settings
.ropeproject
+12
View File
@@ -0,0 +1,12 @@
FROM jfloff/alpine-python:3.4
ENV LIBRARY_PATH=/lib:/usr/lib
ADD src/requirements.txt /
RUN pip install --upgrade pip && pip install -r /requirements.txt
WORKDIR /app
COPY src /app
EXPOSE 5000
CMD ["python", "-u", "/app/main.py"]
+25
View File
@@ -0,0 +1,25 @@
# Yeelight to MQTT bridge
Works with Yeelight WiFi bulbs (color and monochrome).
You need to edit you bulb IPs in main.py (not yet fixed).
Sample docker-compose.yml file:
```
yeelight:
build: .
container_name: yeelight
environment:
- MQTT_SERVER=192.168.1.93
- MQTT_USER=mqtt_user
- MQTT_PASS=passw0rd
restart: always
```
```
docker-compose build && docker-compose up -d
```
## Known bugs:
- Lamp's alive status updated only at startup.
- IP-Name list in main.py
- Bad code style =(
+2
View File
@@ -0,0 +1,2 @@
broadlink
paho-mqtt
+132
View File
@@ -0,0 +1,132 @@
import broadlink
import paho.mqtt.client as mqtt
import os
import time
import threading
from plugstate import PlugState
MQTT_SERVER = os.getenv('MQTT_SERVER', "192.168.1.93")
MQTT_PORT = os.getenv('MQTT_PORT', 1883)
MQTT_USER = os.getenv('MQTT_USER', "hass")
MQTT_PASS = os.getenv('MQTT_PASS', "mqttpass")
QUERY_TIME = os.getenv('QUERY_TIME', 5)
PREFIX = os.getenv('PREFIX', 'home')
ips = dict({
'192.168.1.103':{'name':'kitchen-right', 'mac':bytearray(b'vr\xcb\rC\xb4'), 'type':'plug'},
'192.168.1.183':{'name':'balcony-heater', 'mac':bytearray(b'\xc1j\xcb\rC\xb4'), 'type':'plug'},
'192.168.1.188':{'name':'kitchen-kettle', 'mac':bytearray(b'9V\xcb\rC\xb4'), 'type':'plug'},
'192.168.1.49':{'name':'rm2', 'mac':bytearray(b'U"\xcc\rC\xb4'), 'type':'remote'}
})
plugs=[]
processNow = False
PATH_FMT = PREFIX + "/{model}/{sid}/{prop}" # short_id or sid ?
def prepare_mqtt():
print("Connecting to MQTT server", MQTT_SERVER, ":", MQTT_PORT, "with username", MQTT_USER,":",MQTT_PASS)
client = mqtt.Client()
if (MQTT_USER != "" and MQTT_PASS != ""):
client.username_pw_set(MQTT_USER, MQTT_PASS)
client.connect(MQTT_SERVER, MQTT_PORT, 60)
return client
def push_data(client, model, sid, data):
for key, value in data.items():
path = PATH_FMT.format(model=model,
sid=sid,
prop=key)
client.publish(path, payload=value, qos=0, retain=True)
def init_plugs():
plugs=[]
for ip in ips:
try:
props = ips[ip]
name = props['name']
mac = props['mac']
typeName = props['type']
typeId = 0x2720 #SP2 by default
if (typeName == "remote"):
typeId = 0x2712
broadObj = broadlink.gendevice(typeId, (ip, 80) , mac)
broadObj.auth()
plug = PlugState(ip, typeName, name, broadObj)
plug.update_properties()
plugs.append(plug)
except Exception as e:
print('Connection to ', str(ip) , ' error:', str(e))
return plugs
def refresh_plug_states(data_callback):
global plugs
for plug in plugs:
try:
hashold = plug.hash()
plug.update_properties(force=True)
hashnew = plug.hash()
# print(str(plug.name), hashold, hashnew)
if (hashold != hashnew):
print("!!!! ", plug.name, ":", hashold, "->", hashnew)
data = {'status':plug.status}
if (plug.type == "remote"):
data = {'code':plug.learned_code}
if data_callback is not None:
data_callback(plug.type, plug.name, data)
except Exception as e:
print('Connection to ', str(plug.name) , ' error:', str(e))
def on_mqtt_message(client, userdata, msg):
global processNow
print(msg.topic+" "+str(msg.payload))
parts = msg.topic.split("/")
if (len(parts) != 5):
return
type = parts[1]
if (type != "plug" and type != "remote"):
return
name = parts[2] #name part
param = parts[3] #param part
value = (msg.payload).decode('utf-8')
for plug in plugs:
if (plug.name != name):
continue
plug.process_command(param, value)
processNow=True
def on_connect(client, userdata, rc):
client.subscribe(PREFIX + "/plug/+/+/set")
client.subscribe(PREFIX + "/remote/+/+/learn")
client.subscribe(PREFIX + "/remote/+/+/set")
def refresh_loop(client):
global processNow
cb = lambda m, s, d: push_data(client, m, s, d)
while True:
refresh_plug_states(cb)
for x in range(1,10):
if (processNow):
processNow=False
break
time.sleep(QUERY_TIME/10)
if __name__ == "__main__":
client = prepare_mqtt()
plugs = init_plugs()
print("Founded devices:")
for plug in plugs:
print("Device", plug.name, "on ip", plug.ip, "with type", plug.type)
client.on_message = on_mqtt_message
client.on_connect = on_connect
#start thread for lamp refresh loop
t1 = threading.Thread(target=refresh_loop, args=[client])
t1.start()
# and process mqtt messages in this thread
client.loop_forever()
+78
View File
@@ -0,0 +1,78 @@
import broadlink
import os
import time
import threading
import binascii
class PlugState:
status = "off"
ip = ""
name = ""
type = ""
learned_code = ""
learn_temp = ""
broadlink = None # yeelight object
def __init__(self, ip, typeName, name, broadlinkObj):
self.broadlink = broadlinkObj
self.name = name
self.type = typeName
self.ip = ip
def update_properties(self, force = False):
#need to copy nearned code
if (self.type == "remote" and force):
self.learned_code = self.learn_temp
self.learn_temp = ""
if (self.type == "plug" and force):
state = self.broadlink.check_power()
if (state):
self.status = "on"
else:
self.status = "off"
def hash(self):
return str(self.status) + ":" + str(self.learned_code)
def is_int(self, x):
try:
tmp = int(x)
return True
except Exception as e:
return False
def process_command(self, param, value):
try:
if (param == 'status'):
if (value == "on"):
print("Turning on plug", self.name)
self.broadlink.set_power(True)
if (value == "off"):
print("Turning off plug", self.name)
self.broadlink.set_power(False)
if (param == 'learn' and self.type == 'remote'):
self.broadlink.enter_learning()
time.sleep(5)
ir_packet = self.broadlink.check_data()
#sample data
# ir_packet = bytearray([0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x08 ,0x00 ,0x00 ,0x00 ,0xe0 ,0x07 ,0x25 ,0x32 ,0x07 ,0x01 ,0x03 ,0x0a ,0x00 ,0x00 ,0x00 ,0x00 ,0xc0 ,0xa8 ,0x00 ,0x81 ,0x53 ,0xf1 ,0x00 ,0x00 ,0x3d ,0xc3 ,0x00 ,0x00 ,0x00 ,0x00 ,0x06 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00])
if (ir_packet == None):
print("No ir packets recieved")
return
self.learn_temp = binascii.hexlify(ir_packet)
print("Learned code:", self.learn_temp)
if (param == 'code' and self.type == 'remote'):
if (value == None or value == ""):
print("No ir packets in value")
return
print("Sending IR packet:", value)
#sample data
# ir_packet = bytearray([0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x08 ,0x00 ,0x00 ,0x00 ,0xe0 ,0x07 ,0x25 ,0x32 ,0x07 ,0x01 ,0x03 ,0x0a ,0x00 ,0x00 ,0x00 ,0x00 ,0xc0 ,0xa8 ,0x00 ,0x81 ,0x53 ,0xf1 ,0x00 ,0x00 ,0x3d ,0xc3 ,0x00 ,0x00 ,0x00 ,0x00 ,0x06 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00])
ir_packet = bytes.fromhex(value)
self.broadlink.send_data(ir_packet)
except Exception as e:
print ('Error while set value of plug', self.name , ' error:', e)
+2
View File
@@ -0,0 +1,2 @@
broadlink
paho-mqtt