-
Notifications
You must be signed in to change notification settings - Fork 13
/
mihome.py
126 lines (106 loc) · 4.2 KB
/
mihome.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import binascii
import code
import importlib
import json
import psycopg2
import readline
import socket
import struct
import sys
import time
from Crypto.Cipher import AES
from datetime import datetime
from multiprocessing import Process
from threading import Thread
import config
from plugins import sensor_ht, magnet, yeelight
from utils import get_store
from web.w import run_app as web_app
conn = psycopg2.connect("dbname={} user={} password={} host={} port={}".format(config.DBNAME, config.DBUSER, config.DBPASS, config.DBHOST, config.DBPORT))
cursor = conn.cursor()
MULTICAST = {
'mihome': ('224.0.0.50', 9898),
'yeelight': ('239.255.255.250', 1982)
}
SOCKET_BUFSIZE = 1024
IV = bytes([0x17, 0x99, 0x6d, 0x09, 0x3d, 0x28, 0xdd, 0xb3, 0xba, 0x69, 0x5a, 0x2e, 0x6f, 0x58, 0x56, 0x2e])
def receiver(service='mihome'):
from plugins import gateway
assert service in MULTICAST, 'No such service'
store = get_store()
address, port = MULTICAST.get(service)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(("0.0.0.0", port))
mreq = struct.pack("=4sl", socket.inet_aton(address), socket.INADDR_ANY)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 32)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_LOOP, 1)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, SOCKET_BUFSIZE)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
sock.settimeout(20) # 2x of heartbeat period
current = {}
while True:
try:
data, _ = sock.recvfrom(SOCKET_BUFSIZE) # buffer size is 1024 bytes
except socket.timeout:
continue
print(datetime.now().isoformat(), data)
if service == 'mihome':
message = json.loads(data.decode())
data = json.loads(message['data'])
if message.get('model') in ('sensor_ht', 'weather.v1') and not sensor_ht.process(conn, cursor, current, message, data):
continue
elif message.get('model') == 'magnet':
magnet.process(store, message, data)
elif message.get('model') == 'gateway':
gateway.process(store, message, data)
current = {}
elif service == 'yeelight':
yeelight.process(data.decode())
def send_command(command, timeout=10):
_, port = MULTICAST.get('mihome')
if isinstance(command.get('data'), dict):
command['data'] = json.dumps(command['data'])
address = get_store().get('gateway_addr')
if address is None:
print("Didn't receive any heartbeat from gateway yet. Delaying request for 10 seconds.")
time.sleep(10)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.settimeout(timeout)
sock.connect((address, port))
sock.send(json.dumps(command).encode('ascii'))
data = None
try:
data, addr = sock.recvfrom(SOCKET_BUFSIZE)
except ConnectionRefusedError:
print("send_command :: recvfrom() connection refused: {}:{}".format(address.decode(), port))
except socket.timeout:
print("send_command :: recvfrom() timed out: {}:{}".format(address.decode(), port))
finally:
sock.close()
return data
def get_key():
"""Get current gateway key"""
cipher = AES.new(config.MIHOME_GATEWAY_PASSWORD, AES.MODE_CBC, IV)
encrypted = cipher.encrypt(get_store().get('gateway_token'))
return binascii.hexlify(encrypted)
if __name__ == '__main__':
if len(sys.argv) > 1 and sys.argv[1] == 'shell':
vars = globals().copy()
vars.update(locals())
shell = code.InteractiveConsole(vars)
shell.interact()
sys.exit()
Thread(target=web_app).start()
for app_name in config.ENABLED_APPS:
try:
app = importlib.import_module('apps.{}'.format(app_name))
except ImportError as e:
print('Could not import app "{}": {}'.format(app_name, e))
continue
kwargs = {'store': get_store(), 'conn': conn, 'cursor': cursor}
Process(target=app.run, kwargs=kwargs).start()
print('Loaded app: {}'.format(app_name))
for service in MULTICAST:
Process(target=receiver, args=(service,)).start()
# Discover Yeelight bulbs
yeelight.discover()