Init
This commit is contained in:
+93
@@ -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
@@ -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"]
|
||||
@@ -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 =(
|
||||
@@ -0,0 +1,2 @@
|
||||
broadlink
|
||||
paho-mqtt
|
||||
+132
@@ -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()
|
||||
@@ -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)
|
||||
@@ -0,0 +1,2 @@
|
||||
broadlink
|
||||
paho-mqtt
|
||||
Reference in New Issue
Block a user