#!/usr/bin/env python
# -*- coding: utf-8 -*
from multiprocessing import Process, Lock, Pipe
import multiprocessing, argparse, time, datetime, sys, os, signal, traceback, socket, sched, atexit
import paho.mqtt.client as mqtt  # pip install paho-mqtt
import broadlink  # pip install broadlink
import json  # pip install json
import pytz  # pip install pytz
HAVE_TLS = True
try:
    import ssl
except ImportError:
    HAVE_TLS = False

dirname = os.path.dirname(os.path.abspath(__file__)) + '/'
CONFIG = os.getenv('BROADLINKMQTTCONFIG', dirname + 'broadlink-thermostat.conf')

class Config(object):
    def __init__(self, filename=CONFIG):
        self.config = {}
        self.config['ca_certs']     = None
        self.config['tls_version']  = None
        self.config['certfile']     = None
        self.config['keyfile']      = None
        self.config['tls_insecure'] = False
        self.config['tls']          = False
        execfile(filename, self.config)

        if HAVE_TLS == False:
            logging.error("TLS parameters set but no TLS available (SSL)")
            sys.exit(2)

        if self.config.get('ca_certs') is not None:
            self.config['tls'] = True

        if self.config.get('tls_version') is not None:
            if self.config.get('tls_version') == 'tlsv1':
                self.config['tls_version'] = ssl.PROTOCOL_TLSv1
            if self.config.get('tls_version') == 'tlsv1.2':
                # TLS v1.2 is available starting from python 2.7.9 and requires openssl version 1.0.1+.
                if sys.version_info >= (2,7,9):
                    self.config['tls_version'] = ssl.PROTOCOL_TLSv1_2
                else:
                    logging.error("TLS version 1.2 not available but 'tlsv1.2' is set.")
                    sys.exit(2)
            if self.config.get('tls_version') == 'sslv3':
                self.config['tls_version'] = ssl.PROTOCOL_SSLv3

    def get(self, key, default='special empty value'):
        v = self.config.get(key, default)
        if v == 'special empty value':
            logging.error("Configuration parameter '%s' should be specified" % key)
            sys.exit(2)
        return v
        
def unhandeledException(e):
    trace = open('/tmp/transfer-unhandled-exception.log', 'w+')
    traceback.print_exc(None, trace)
    trace.close()

class ReadDevice(Process):
    def __init__(self, pipe, divicemac, device, conf):
        super(ReadDevice, self).__init__()
        self.pipe         = pipe
        self.divicemac    = divicemac
        self.device       = device
        self.conf         = conf


    def run(self):
        print('PID child %d' % os.getpid())
        mqttc= mqtt.Client(self.conf.get('mqtt_clientid', 'broadlink')+'-%s-%s' % (self.divicemac,os.getpid()), clean_session=self.conf.get('mqtt_clean_session', False))
        mqttc.will_set('%s/%s'%(self.conf.get('mqtt_topic_prefix', '/broadlink'), self.divicemac), payload="Disconnect", qos=self.conf.get('mqtt_qos', 0), retain=False)
        mqttc.reconnect_delay_set(min_delay=3, max_delay=30)
        if self.conf.get('tls') == True:
            mqttc.tls_set(self.conf.get('ca_certs'), self.conf.get('certfile'), self.conf.get('keyfile'), tls_version=self.conf.get('tls_version'), ciphers=None)
        if self.conf.get('tls_insecure'):
            mqttc.tls_insecure_set(True)
        mqttc.username_pw_set(self.conf.get('mqtt_username'), self.conf.get('mqtt_password'))
        mqttc.connect(self.conf.get('mqtt_broker', 'localhost'), int(self.conf.get('mqtt_port', '1883')), 60)
        mqttc.loop_start()
        try:
            if self.device.auth():
                self.run = True
                print self.device.type
                timezone = pytz.timezone(self.conf.get('time_zone','Europe/Berlin'))
                now=datetime.datetime.now(timezone)
                # set device time
                self.device.set_time(now.hour, now.minute, now.second, now.weekday()+1)
                print('set time %d:%d:%d %d' % (now.hour, now.minute, now.second, now.weekday()+1))
                # set auto_mode = 0, loop_mode = 0 ("12345,67")
                self.device.set_mode(self.conf.get('auto_mode', 0), self.conf.get('loop_mode', 0))
                # set device on, remote_lock off
                self.device.set_power(1, self.conf.get('remote_lock', 0))
            else:
                self.run = False
            while self.run:
                try:
                    if self.pipe.poll(self.conf.get('loop_time', 60)):
                        result = self.pipe.recv()
                        if type(result) == tuple and len(result) == 2:
                            (cmd, opts) = result
                            if cmd=='set_temp' and float(opts)>0:
                                self.device.set_temp(float(opts))
                            elif cmd=='set_mode':
                                self.device.set_mode(0 if int(opts) == 0 else 1, self.conf.get('loop_mode', 0))
                            elif cmd=='set_power':
                                self.device.set_power(0 if int(opts) == 0 else 1, self.conf.get('remote_lock', 0))
                            elif cmd=='switch_to_auto':
                                self.device.switch_to_auto()
                            elif cmd=='switch_to_manual':
                                self.device.switch_to_manual()
                            elif cmd=='set_schedule':
                                try:
                                    schedule=json.loads(opts)
                                    self.device.set_schedule(schedule[0],schedule[1])
                                except Exception, e:
                                    pass
                        else:
                            if result == 'STOP':
                                self.shutdown()
                                mqttc.loop_stop()
                                return
                    else:
                        try:
                            data = self.device.get_full_status()
                        except socket.timeout:
                            mqttc.loop_stop()
                            return
                        except Exception, e:
                            unhandeledException(e)
                            mqttc.loop_stop()
                            return
                        for key in data:
                            if type(data[key]).__name__ == 'list':
                                mqttc.publish('%s/%s/%s'%(self.conf.get('mqtt_topic_prefix', '/broadlink'), self.divicemac, key), json.dumps(data[key]), qos=self.conf.get('mqtt_qos', 0), retain=self.conf.get('mqtt_retain', False))
                                pass
                            else:
                                if key == 'room_temp':
                                    print "  {} {} {}".format(self.divicemac, key, data[key])
                                mqttc.publish('%s/%s/%s'%(self.conf.get('mqtt_topic_prefix', '/broadlink'), self.divicemac, key), data[key], qos=self.conf.get('mqtt_qos', 0), retain=self.conf.get('mqtt_retain', False))
                        mqttc.publish('%s/%s/%s'%(self.conf.get('mqtt_topic_prefix', '/broadlink'), self.divicemac, 'schedule'), json.dumps([data['weekday'],data['weekend']]), qos=self.conf.get('mqtt_qos', 0), retain=self.conf.get('mqtt_retain', False))
                except Exception, e:
                    unhandeledException(e)
                    mqttc.loop_stop()
                    return
            mqttc.loop_stop()
            return

        except KeyboardInterrupt:
            mqttc.loop_stop()
            return

        except Exception, e:
            unhandeledException(e)
            mqttc.loop_stop()
            return

def main():
    try:
        conf = Config()
    except Exception, e:
        print "Cannot load configuration from file %s: %s" % (CONFIG, str(e))
        sys.exit(2)

    jobs = []
    pipes = []
    founddevices = {}
    
    def on_message(client, pipes, msg):
        topic = msg.topic.replace(conf.get('mqtt_topic_prefix', '/broadlink'),'')
        cmd = topic.split('/')
        devicemac = cmd[1]
        command = cmd[3]
        if cmd[2] == 'cmd':
            print "Received command %s for device %s" % (command, devicemac)
            try:
                for (ID, pipe) in pipes:
                    if ID==devicemac:
                        print 'send to pipe %s' % ID
                        pipe.send((command, msg.payload))
            except:
                print "Unexpected error:", sys.exc_info()[0]
                raise

    def on_disconnect(client, empty, rc):
        print("Disconnect, reason: " + str(rc))
        print("Disconnect, reason: " + str(client))
        client.loop_stop()
        time.sleep(10)
        
    def on_kill(mqttc, jobs):
        mqttc.loop_stop()
        for j in jobs:
            j.join()

    def on_connect(client, userdata, flags, rc):
        client.publish(conf.get('mqtt_topic_prefix', '/broadlink'), 'Connect')
        print("Connect, reason: " + str(rc))

    def on_log(mosq, obj, level, string):
        print(string)

    mqttc = mqtt.Client(conf.get('mqtt_clientid', 'broadlink')+'-%s'%os.getpid(), clean_session=conf.get('mqtt_clean_session', False), userdata=pipes)
    mqttc.on_message = on_message
    mqttc.on_connect = on_connect
    mqttc.on_disconnect = on_disconnect
    #mqttc.on_log = on_log
    mqttc.will_set(conf.get('mqtt_topic_prefix', '/broadlink'), payload="Disconnect", qos=conf.get('mqtt_qos', 0), retain=False)
    mqttc.reconnect_delay_set(min_delay=3, max_delay=30)
    if conf.get('tls') == True:
        mqttc.tls_set(conf.get('ca_certs'), conf.get('certfile'), conf.get('keyfile'), tls_version=conf.get('tls_version'), ciphers=None)
    if conf.get('tls_insecure'):
        mqttc.tls_insecure_set(True)
    mqttc.username_pw_set(conf.get('mqtt_username'), conf.get('mqtt_password'))
    mqttc.connect(conf.get('mqtt_broker', 'localhost'), int(conf.get('mqtt_port', '1883')), 60)
    mqttc.subscribe(conf.get('mqtt_topic_prefix', '/broadlink') + '/+/cmd/#', qos=conf.get('mqtt_qos', 0))

    atexit.register(on_kill, mqttc, jobs)
    
    run = True
    while run:
        try:
            for idx, j in enumerate(jobs):
                if not j.is_alive():
                    try:
                        print "Killing job."
                        j.join()
                    except:
                        print "Error killing job."
                        pass
                    try:
                        print "Deleting pipe from pipes array"
                        for idy, (ID, pipe) in enumerate(pipes):
                            if ID==founddevices[j.pid]:
                                del pipes[idy]
                    except:
                        print "Error deleting pipe from pipes array"
                    try:
                        print "Deleting device from founddevices array."
                        del founddevices[j.pid]
                    except:
                        print "Error deleting device from founddevices array."
                        pass
                    try:
                        print "Deleting job from jobs array."
                        del jobs[idx]
                    except:
                        print "Error deleting job from jobs array."
                        pass
            print "broadlink discover"
            devices = broadlink.discover(timeout=conf.get('lookup_timeout', 5))
            for device in devices:
                divicemac = ''.join(format(x, '02x') for x in device.mac)
                if divicemac not in founddevices.values():
                    transportPipe, MasterPipe = Pipe()
                    pipes.append((divicemac, MasterPipe))

                    print "found: {} {}".format(device.host[0], ''.join(format(x, '02x') for x in device.mac))
                    p = ReadDevice(transportPipe, divicemac, device, conf)
                    jobs.append(p)
                    p.start()
                    founddevices[p.pid] = divicemac
                    time.sleep(2)
            mqttc.user_data_set(pipes)
            mqttc.loop_stop()
            print "Reconnect"
            mqttc.loop_start()
            time.sleep(conf.get('rediscover_time', 600))
        except KeyboardInterrupt:
            run = False
        except Exception, e:
            run = False
            unhandeledException(e)
        except SignalException_SIGKILL:
            run = False

    mqttc.loop_stop()
    for j in jobs:
        j.join()
    return

main()
