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={}".format(config.DBNAME, config.DBUSER, config.DBPASS)) 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()