Example #1
0
def slave_conf_copy():
    file_from = DaemonConfigParser(SLAVE_CONF_FILE_FROM);
    file_to   = DaemonConfigParser(SLAVE_CONF_FILE_TO);
    
    #listen
    var = file_from.getValueFromSection('listen', 'port');
    file_to.writeValueFromSection('listen', 'port', var);
    
    #connect
    var = file_from.getValueFromSection('connect', 'port');
    file_to.writeValueFromSection('connect', 'port', var);
    
    #knx
    var = file_from.getValueFromSection('knx', 'port');
    file_to.writeValueFromSection('knx', 'port', var);
    var = file_from.getValueFromSection('knx', 'interface');
    file_to.writeValueFromSection('knx', 'interface', var);
    
    #enocean
    var = file_from.getValueFromSection('enocean', 'port');
    file_to.writeValueFromSection('enocean', 'port', var);
    var = file_from.getValueFromSection('enocean', 'interface');
    file_to.writeValueFromSection('enocean', 'interface', var);
    
    #cron
    var = file_from.getValueFromSection('cron', 'port');
    file_to.writeValueFromSection('cron', 'port', var);
    var = file_from.getValueFromSection('cron', 'address');
    file_to.writeValueFromSection('cron', 'address', var);
    
    #personnal_key
    var = file_from.getValueFromSection('personnal_key', 'aes');
    file_to.writeValueFromSection('personnal_key', 'aes', var);
Example #2
0
def master_conf_copy():
    file_from = DaemonConfigParser(MASTER_CONF_FILE_BKP)
    file_to = DaemonConfigParser(MASTER_CONF_FILE_TO)

    # listen
    var = file_from.getValueFromSection("listen", "port_slave")
    file_to.writeValueFromSection("listen", "port_slave", var)
    var = file_from.getValueFromSection("listen", "port_cmd")
    file_to.writeValueFromSection("listen", "port_cmd", var)

    # connect
    var = file_from.getValueFromSection("connect", "port")
    file_to.writeValueFromSection("connect", "port", var)

    # mysql
    var = file_from.getValueFromSection("mysql", "user")
    file_to.writeValueFromSection("mysql", "user", var)
    var = file_from.getValueFromSection("mysql", "database_name")
    file_to.writeValueFromSection("mysql", "database_name", var)

    # greenleaf
    var = file_from.getValueFromSection("greenleaf", "commercial")
    file_to.writeValueFromSection("greenleaf", "commercial", var)
    var = file_from.getValueFromSection("greenleaf", "admin_addr")
    file_to.writeValueFromSection("greenleaf", "admin_addr", var)
Example #3
0
def master_conf_copy():
    file_from = DaemonConfigParser(MASTER_CONF_FILE_BKP);
    file_to   = DaemonConfigParser(MASTER_CONF_FILE_TO);
    
    #listen
    var = file_from.getValueFromSection('listen', 'port_slave');
    file_to.writeValueFromSection('listen', 'port_slave', var);
    var = file_from.getValueFromSection('listen', 'port_cmd');
    file_to.writeValueFromSection('listen', 'port_cmd', var);
    
    #connect
    var = file_from.getValueFromSection('connect', 'port');
    file_to.writeValueFromSection('connect', 'port', var);
    
    #mysql
    var = file_from.getValueFromSection('mysql', 'user');
    file_to.writeValueFromSection('mysql', 'user', var);
    var = file_from.getValueFromSection('mysql', 'database_name');
    file_to.writeValueFromSection('mysql', 'database_name', var);
    
    #greenleaf
    var = file_from.getValueFromSection('greenleaf', 'commercial');
    file_to.writeValueFromSection('greenleaf', 'commercial', var);
    var = file_from.getValueFromSection('greenleaf', 'admin_addr');
    file_to.writeValueFromSection('greenleaf', 'admin_addr', var);
Example #4
0
 def send_cron(cron_name):
     try:
         parser = DaemonConfigParser(SLAVE_CONF_FILE);
         port = parser.getValueFromSection('cron', 'port').encode()
         sock = socket.create_connection(('127.0.0.1', port));
         sock.send(bytes(cron_name, 'utf-8'));
         sock.close();
     except Exception as e:
         if 'sock' in locals():
             sock.close()
Example #5
0
def master_conf_init():
    file = DaemonConfigParser(SLAVE_CONF_FILE)
    personnal_key = file.getValueFromSection("personnal_key", "aes")
    hostname = socket.gethostname()

    # KNX Interface
    if os.path.exists("/dev/ttyAMA0"):
        knx = "tpuarts"
        knx_interface = "ttyAMA0"
    elif os.path.exists("/dev/ttyS0"):
        knx = "tpuarts"
        knx_interface = "ttyS0"
    else:
        knx = "ipt"
        knx_interface = "127.0.0.1"

    fic = open("/etc/domoleaf/.domoslave.version", "r")
    domoslave = fic.readline()
    fic.close()

    personnal_key = md5(personnal_key.encode("utf-8"))

    query1 = (
        "INSERT INTO daemon (name, serial, secretkey, validation, version) VALUES ('"
        + hostname
        + "','"
        + hostname
        + "','"
        + personnal_key.hexdigest()
        + "',1,'"
        + domoslave.split("\n")[0]
        + "')"
    )
    query2 = (
        "INSERT INTO daemon_protocol (daemon_id, protocol_id, interface, interface_arg) VALUES (1,1,'"
        + knx
        + "','"
        + knx_interface
        + "')"
    )
    Popen(
        ["mysql", "--defaults-file=/etc/mysql/debian.cnf", "domoleaf", "-e", query1],
        stdin=PIPE,
        stdout=PIPE,
        stderr=PIPE,
        bufsize=-1,
    )
    Popen(
        ["mysql", "--defaults-file=/etc/mysql/debian.cnf", "domoleaf", "-e", query2],
        stdin=PIPE,
        stdout=PIPE,
        stderr=PIPE,
        bufsize=-1,
    )
Example #6
0
 def ipVPN():
     private = InfoSys.ipPrivate();
     parser = DaemonConfigParser(SLAVE_CONF_FILE);
     server = parser.getValueFromSection('openvpn', 'openvpnserver').encode()
     if server.decode() == 'none':
         return '';
     try:
         vpn = ([(s.connect((server, 80)), s.getsockname()[0], s.close()) for s in [socket.socket(socket.AF_INET, socket.SOCK_DGRAM)]][0][1])
     except Exception as e:
         return ''
     if private != vpn:
         return vpn;
     return '';
def master_conf_init():
    file = DaemonConfigParser(SLAVE_CONF_FILE);
    personnal_key = file.getValueFromSection('personnal_key', 'aes');
    hostname = socket.gethostname();
    #KNX Interface
    if os.path.exists('/dev/ttyAMA0'):
        knx = "tpuarts"
        knx_interface = 'ttyAMA0';
    elif os.path.exists('/dev/ttyS0'):
        knx = "tpuarts"
        knx_interface = 'ttyS0';
    else:
        knx = "ipt"
        knx_interface = '127.0.0.1';
    domoslave = os.popen("dpkg-query -W -f='${Version}\n' domoslave").read().split('\n')[0];
    query1 = "INSERT INTO daemon (name, serial, secretkey, validation, version) VALUES ('"+hostname+"','"+hostname+"','"+personnal_key+"',1,'"+domoslave+"')"
    query2 = "INSERT INTO daemon_protocol (daemon_id, protocol_id, interface, interface_arg) VALUES (1,1,'"+knx+"','"+knx_interface+"')"
    call(['mysql', '--defaults-file=/etc/mysql/debian.cnf', 'domoleaf',
           '-e', query1]);
    call(['mysql', '--defaults-file=/etc/mysql/debian.cnf', 'domoleaf',
           '-e', query2]);
Example #8
0
class SlaveDaemon:

    ## The constructor
    #
    # @param log_flag The flag saying whether the logs should be printing or not
    def __init__(self, log_flag):
        ## Logger object for formatting and printing logs
        self.logger = Logger(log_flag, LOG_FILE)
        self.logger.info('Started Domoleaf Slave daemon')
        ## Array of master daemon on local network
        self.connected_masters = {}
        ## Array of monitor KNX on local network
        self.connected_knx = []
        ## Array of monitor EnOcean on local network
        self.connected_enocean = []
        ## Array of cron running on the system
        self.connected_cron = []
        self._scanner = Scanner(log_flag)
        self._hostlist = []
        myhostname = socket.gethostname().upper()
        if SLAVE_NAME_PREFIX in myhostname:
            self._scanner.scan()
            self._hostlist = self._scanner._HostList
        else:
            self._hostlist.append(Host('', '127.0.0.1', myhostname))
        self._parser = DaemonConfigParser(SLAVE_CONF_FILE)
        ## Keys for encrypting communications
        self.encrypt_keys = {}
        ## Main socket for communication with KNX daemon
        self.knx_sock = None
        ## Main socket for communication with master daemon
        self.master_sock = None
        ## Main socket for communication with enocean daemon
        self.enocean_sock = None
        ## Main socket for communication with cron
        self.cron_sock = None
        ## Private AES key got from configuration file
        self.private_aes = self._parser.getValueFromSection(
            'personnal_key', 'aes')
        self.wifi_init(self._parser.getValueFromSection('wifi', 'ssid'),
                       self._parser.getValueFromSection('wifi', 'password'),
                       self._parser.getValueFromSection('wifi', 'encryption'),
                       self._parser.getValueFromSection('wifi', 'mode'), 0)
        ## Port on which connect got from configuration file
        self.connect_port = self._parser.getValueFromSection(
            SLAVE_CONF_CONNECT_SECTION, SLAVE_CONF_CONNECT_PORT_ENTRY)
        ## Callback array indexed on packet type
        self.functions = {
            KNX_READ_REQUEST: self.knx_read_request,
            KNX_WRITE_SHORT: self.knx_write_short,
            KNX_WRITE_LONG: self.knx_write_long,
            KNX_WRITE_TEMP: self.knx_write_temp,
            CHECK_SLAVE: self.check_slave,
            MONITOR_IP: self.monitor_ip,
            DATA_UPDATE: self.update,
            SEND_TECH: self.send_tech,
            SEND_ALIVE: self.send_alive,
            SEND_INTERFACES: self.send_interfaces,
            SHUTDOWN_D3: self.shutdown_d3,
            REBOOT_D3: self.reboot_d3,
            WIFI_UPDATE: self.wifi_update
        }

    ## Updates the base system of the slave daemon.
    #
    # @param json_obj Not used here.
    # @param connection Not used here.
    # @return None
    def update(self, json_obj, connection):
        p = call(['dpkg', '--configure', '-a'])
        call(['apt-get', 'update'])
        call([
            'DEBIAN_FRONTEND=noninteractive', 'apt-get', 'install',
            'domoslave', '-y'
        ])
        version = os.popen(
            "dpkg-query -W -f='${Version}\n' domoslave").read().split('\n')[0]
        json_str = '{"packet_type": "update_finished", "aes_pass": "******", "new_version": ' + version + '}'
        encrypt_IV = AESManager.get_IV()
        spaces = 16 - len(json_str) % 16
        json_str = json_str + (spaces * ' ')
        encode_obj = AES.new(self.private_aes, AES.MODE_CBC, encrypt_IV)
        data = encode_obj.encrypt(json_str)
        # faut ouvrir une nouvelle socket pour envoyer la nouvelle version
        # connection.send(bytes(encrypt_IV, 'utf-8') + data);

    ## Initializes the sockets for listenning incomming connections.
    #
    # @return None
    def run(self):
        ## Run flag at True for running, at False to stop the main loop
        self.run = True
        self.knx_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.master_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.enocean_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.cron_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.knx_sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
        self.master_sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
        self.enocean_sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
        self.cron_sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
        port = self._parser.getValueFromSection(SLAVE_CONF_KNX_SECTION,
                                                SLAVE_CONF_KNX_PORT_ENTRY)
        if not port:
            sys.exit(2)
        port_master = self._parser.getValueFromSection(
            SLAVE_CONF_LISTEN_SECTION, SLAVE_CONF_LISTEN_PORT_ENTRY)
        if not port_master:
            sys.exit(2)
        port_enocean = self._parser.getValueFromSection(
            SLAVE_CONF_ENOCEAN_SECTION, SLAVE_CONF_ENOCEAN_PORT_ENTRY)
        if not port_enocean:
            sys.exit(2)
        port_cron = self._parser.getValueFromSection(
            SLAVE_CONF_CRON_SECTION, SLAVE_CONF_CRON_PORT_ENTRY)
        if not port_cron:
            sys.exit(2)
        self.knx_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.master_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.enocean_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.cron_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.knx_sock.bind(('', int(port)))
        self.master_sock.bind(('', int(port_master)))
        self.enocean_sock.bind(('', int(port_enocean)))
        self.cron_sock.bind(('127.0.0.1', int(port_cron)))
        self.knx_sock.listen(MAX_KNX)
        self.master_sock.listen(MAX_MASTERS)
        self.enocean_sock.listen(MAX_ENOCEAN)
        self.cron_sock.listen(MAX_CRON)
        self.send_monitor_ip()
        self.loop()

    ## Gets available sockets for reading on the KNX socket.
    #
    # @return None
    def accept_knx(self):
        rlist, wlist, elist = select.select([self.knx_sock], [], [],
                                            SELECT_TIMEOUT)
        append = self.connected_knx.append
        for connection in rlist:
            new_knx, addr = connection.accept()
            append(new_knx)
        self.receive_from_knx(self.connected_knx)

    ## Gets available sockets for reading on the master socket.
    #
    # @return None
    def accept_masters(self):
        rlist, wlist, elist = select.select([self.master_sock], [], [],
                                            SELECT_TIMEOUT)
        masters_socks = []
        append = masters_socks.append
        for item in rlist:
            new_conn, addr = item.accept()
            append(new_conn)
        self.receive_from_masters(masters_socks)

    ## Gets available sockets for reading on the EnOcean socket.
    #
    # @return None
    def accept_enocean(self):
        rlist, wlist, elist = select.select([self.enocean_sock], [], [],
                                            SELECT_TIMEOUT)
        enocean_socks = []
        append = enocean_socks.append
        append_connected = self.connected_enocean.append
        for item in rlist:
            new_conn, addr = item.accept()
            append(new_conn)
            append_connected(new_conn)
        self.receive_from_enocean(enocean_socks)

    ## Gets available sockets for reading on the Cron socket.
    #
    # @return None
    def accept_cron(self):
        rlist, wlist, elist = select.select([self.cron_sock], [], [],
                                            SELECT_TIMEOUT)
        cron_socks = []
        append = cron_socks.append
        append_connected = self.connected_cron.append
        for item in rlist:
            new_conn, addr = item.accept()
            append(new_conn)
            append_connected(new_conn)
        self.receive_from_cron(cron_socks)

    ## Checks the packet_type of the data, and calls the appropriate function.
    #
    # @param data Packet data including the packet type.
    # @param connection Connection object sent to the function.
    # @return None
    def parse_data(self, data, connection):
        json_obj = json.JSONDecoder().decode(data)
        if json_obj['packet_type'] in self.functions.keys():
            self.functions[json_obj['packet_type']](json_obj, connection)
        else:
            raise Exception(
                str(json_obj['packet_type']) + ": is not a valid packet type")

    ## System call of 'groupread' with parameters.
    #
    # @param json_obj JSON Object containing the address to read.
    # @param connection Not used here.
    # @return None
    def knx_read_request(self, json_obj, connection):
        call(['knxtool', CALL_GROUPREAD, EIB_URL, json_obj['addr_to_read']])

    ## System call of "groupwrite" with parameters.
    #
    # @param json_obj JSON Object containing the address to which send, and the values to send.
    # @param connection Not used here.
    # @return None
    def knx_write_temp(self, json_obj, connection):
        val = json_obj['value'].split(' ')
        call([
            'knxtool', CALL_GROUPWRITE, EIB_URL, json_obj['addr_to_send'],
            val[0], val[1]
        ])

    ## System call of "groupswrite" with parameters.
    #
    # @param json_obj JSON Object containing the address to which send, and the value to send.
    # @param connection Not used here.
    # @return None
    def knx_write_short(self, json_obj, connection):
        call([
            'knxtool', CALL_GROUPSWRITE, EIB_URL, json_obj['addr_to_send'],
            str(json_obj['value'])
        ])

    ## System call of "groupwrite" with parameters.
    #
    # @param json_obj JSON Object containing the address to which send, and the value to send.
    # @param connection Not used here.
    # @return None
    def knx_write_long(self, json_obj, connection):
        call([
            'knxtool', CALL_GROUPWRITE, EIB_URL, json_obj['addr_to_send'],
            str(json_obj['value'])
        ])

    ## Reads data comming from amsters and calls parse_data().
    #
    # @param masters_to_read Array containing the sockets of all the masters found on local network.
    # @return None
    def receive_from_masters(self, masters_to_read):
        for master in masters_to_read:
            data = master.recv(4096)
            decrypt_IV = data[:16].decode()
            decode_obj = AES.new(self.private_aes, AES.MODE_CBC, decrypt_IV)
            data2 = decode_obj.decrypt(data[16:])
            self.parse_data(data2.decode(), master)
            master.close()

    ## Reads data from monitor KNX and transmits to master.
    #
    # @param knx_to_read Array containing the sockets of all the KNX daemons on local network.
    # @return None
    def receive_from_knx(self, knx_to_read):
        for knx in knx_to_read:
            data = knx.recv(TELEGRAM_LENGTH)
            if data:
                self.send_knx_data_to_masters(data)
            if knx in self.connected_knx:
                knx.close()
                self.connected_knx.remove(knx)

    ## Reads data from monitor EnOcean and transmits to master.
    #
    # @param enocean_to_read Array containing the sockets of all the EnOcean daemons on local network.
    # @return None
    def receive_from_enocean(self, enocean_to_read):
        for enocean in enocean_to_read:
            data = enocean.recv(4096)
            if data:
                self.send_enocean_data_to_masters(data)
            if enocean in self.connected_enocean:
                enocean.close()
                self.connected_enocean.remove(enocean)

    ## Receives data from a cron and executes it.
    #
    # @param cron_to_read Array containing the sockets of all crons on local network.
    # @return None
    def receive_from_cron(self, cron_to_read):
        for cron in cron_to_read:
            data = cron.recv(4096)
            if data:
                json_str = json.JSONEncoder().encode(
                    {"packet_type": data.decode()})
                self.parse_data(json_str, cron)
            if cron in self.connected_cron:
                cron.close()
                self.connected_cron.remove(cron)

    ## Checks the existence of this daemon.
    # This function is called when a check_slave packet is received.
    #
    # @param json_obj JSON Object containing the hostname of the sender of the packet.
    # @param connection Connection object used to send the response.
    # @return None
    def check_slave(self, json_obj, connection):
        interface_knx = self._parser.getValueFromSection(
            SLAVE_CONF_KNX_SECTION, SLAVE_CONF_KNX_INTERFACE)
        interface_enocean = self._parser.getValueFromSection(
            SLAVE_CONF_ENOCEAN_SECTION, SLAVE_CONF_ENOCEAN_INTERFACE)
        version = os.popen(
            "dpkg-query -W -f='${Version}\n' domoslave").read().split('\n')[0]
        json_str = '{"packet_type": "check_slave", "aes_pass": "******", "version": "' + version + '", "interface_knx": "' + interface_knx + '", "interface_enocean": "' + interface_enocean + '"}'
        master_hostname = str(json_obj['sender_name'])
        encrypt_IV = AESManager.get_IV()
        spaces = 16 - len(json_str) % 16
        json_str = json_str + (spaces * ' ')
        encode_obj = AES.new(self.private_aes, AES.MODE_CBC, encrypt_IV)
        data = encode_obj.encrypt(json_str)
        connection.send(bytes(encrypt_IV, 'utf-8') + data)

    ## Re scans the local network and refreshes hostlist.
    #
    # @param json_obj Not used here.
    # @param connection Not used here.
    # @return None
    def monitor_ip(self, json_obj, connection):
        self._scanner.scan()
        self._hostlist = self._scanner._HostList

    ## Sends a monitor_ip packet to all the masters available.
    #
    # @return None
    def send_monitor_ip(self):
        json_str = json.JSONEncoder().encode({"packet_type": "monitor_ip"})
        self.send_data_to_all_masters(json_str)

    ## Main daemon loop.
    #
    # @return None
    def loop(self):
        while self.run:
            try:
                self.accept_knx()
            except Exception as e:
                frameinfo = getframeinfo(currentframe())
                self.logger.error('in loop accept_knx: ' + str(e))
            except KeyboardInterrupt as e:
                frameinfo = getframeinfo(currentframe())
                self.logger.error('in loop: Keyboard interrupt')
            try:
                self.accept_masters()
            except Exception as e:
                frameinfo = getframeinfo(currentframe())
                self.logger.error('in loop accept_masters: ' + str(e))
            except KeyboardInterrupt as e:
                frameinfo = getframeinfo(currentframe())
                self.logger.error('in loop: Keyboard interrupt')
            try:
                self.accept_enocean()
            except Exception as e:
                frameinfo = getframeinfo(currentframe())
                self.logger.error('in loop accept_enocean: ' + str(e))
            except KeyboardInterrupt as e:
                frameinfo = getframeinfo(currentframe())
                self.logger.error('in loop: Keyboard interrupt')
            try:
                self.accept_cron()
            except Exception as e:
                frameinfo = getframeinfo(currentframe())
                self.logger.error('in loop accept_cron: ' + str(e))
            except KeyboardInterrupt as e:
                frameinfo = getframeinfo(currentframe())
                self.logger.error('in loop: Keyboard interrupt')

    ## Stops the daemon and closes all sockets.
    #
    # @return None
    def stop(self):
        for name, sock in self.connected_masters.items():
            sock.close()
        for knx in self.connected_knx:
            knx.close()
        self.knx_sock.close()

    ## Stores every host on local network if its hostname begins by 'MD3' in connected_masters dict().
    #
    # @return None
    def connect_to_masters(self):
        hostname = socket.gethostname()
        self.connected_masters = {}
        for host in self._hostlist:
            if MASTER_NAME_PREFIX in host._Hostname or str(
                    host._IpAddr) == '127.0.0.1':
                if not self.connect_port:
                    self.logger.error('in connect_to_masters: No ' +
                                      SLAVE_CONF_CONNECT_PORT_ENTRY + ' in ' +
                                      SLAVE_CONF_CONNECT_SECTION +
                                      ' section or maybe no such ' +
                                      SLAVE_CONF_CONNECT_SECTION + ' defined')
                    sys.exit(1)
                try:
                    self.logger.debug('Connecting to ' + str(host._IpAddr) +
                                      ':' + str(self.connect_port))
                    sock = socket.create_connection(
                        (host._IpAddr, self.connect_port))
                    hostname = host._Hostname.split('.')[0]
                    self.connected_masters[host._Hostname] = sock
                except Exception as e:
                    frameinfo = getframeinfo(currentframe())
                    self.logger.error('in connect_to_masters: ' + str(e))
                    pass

    ## Converts data from bytes to a clear KNX datagram, and sends it to all available masters.
    #
    # @param data The data having to be converted from bytes to clear KNX datagram.
    # @return None
    def send_knx_data_to_masters(self, data):
        ctrl = int(data[0])
        src_addr = int.from_bytes(data[1:3], byteorder='big')
        dst_addr = int.from_bytes(data[3:5], byteorder='big')
        data_len = int.from_bytes(data[5:6], byteorder='big')
        telegram_data = data[6:7 + data_len]
        typ = -1
        value = 0
        if telegram_data[1] & 0xC0 == 0x00:  # read
            typ = 0
        elif telegram_data[1] & 0xC0 == 0x40:  # resp
            typ = 1
            if data_len == 2:
                value = int(telegram_data[1] & 0x0f)
            elif data_len > 2:
                value = int.from_bytes(telegram_data[2:data_len],
                                       byteorder='big')
        elif telegram_data[1] & 0xC0 == 0x80:  # write
            typ = 2
            if data_len == 2:
                value = int(telegram_data[1] & 0x0f)
            elif data_len > 2:
                typ = 3
                value = int.from_bytes(telegram_data[2:data_len],
                                       byteorder='big')
        json_str = json.JSONEncoder().encode({
            "packet_type":
            "monitor_knx",
            "type":
            typ,
            "src_addr":
            individual2string(src_addr),
            "dst_addr":
            group2string(dst_addr),
            "date":
            str(time.time()).split('.')[0],
            "value":
            value,
            "sender_name":
            socket.gethostname()
        })
        self.send_data_to_all_masters(json_str)

    ## Convertes data from bytes to a clear EnOcean datagram, and sends it to available masters.
    #
    # @param data The data having to be converted from bytes to EnOcean datagram.
    # @return None
    def send_enocean_data_to_masters(self, data):
        if (data[4] == PACKET_TYPE_RADIO_ERP1
            ):  # si le packet_type == radio_erp1
            data_len = int.from_bytes(data[1:2], byteorder='big')
            opt_data_len = int(data[3])
            src_str = "%X" % int.from_bytes(data[1 + data_len:5 + data_len],
                                            byteorder='big')
            if len(src_str) < 8:
                src_str = "0" + src_str
            json_dict = {
                "packet_type":
                "monitor_enocean",
                "src_addr":
                src_str,
                "dst_addr":
                "%X" %
                int.from_bytes(data[261:265 + opt_data_len], byteorder='big'),
                "date":
                str(time.time()).split('.')[0],
                "sender_name":
                socket.gethostname(),
                "type":
                int(data[6])
            }
            if data[6] == RORG_NORMAL:
                json_dict['value'] = int(data[7])
            elif data[6] == RORG_TEMPERATURE:
                json_dict['value'] = float(40 - ((40 / 255) * int(data[9])))
            json_str = json.JSONEncoder().encode(json_dict)
            self.send_data_to_all_masters(json_str)

    ## Sends data to all masters available on local network.
    #
    # @param json_str The data to send under form of a JSON Object stringified.
    # @return None
    def send_data_to_all_masters(self, json_str):
        self.connect_to_masters()
        for name in self.connected_masters.keys():
            try:
                master = self.connected_masters[name]
                AES.key_size = 32
                aes_IV = AESManager.get_IV()
                encode_obj = AES.new(self.private_aes, AES.MODE_CBC, aes_IV)
                spaces = 16 - len(json_str) % 16
                data2 = encode_obj.encrypt(json_str + (spaces * ' '))
                master.send(bytes(aes_IV, 'utf-8') + data2)
                master.close()
            except KeyError as e:
                self.logger.error('in send_data_to_all_masters: ' + str(e))
                pass

    ## Sends the informations about the slave to all masters available.
    #
    # @param json_obj Not used here.
    # @param connection Not used here.
    # @return None
    def send_tech(self, json_obj, connection):
        json_str = json.JSONEncoder().encode({
            "packet_type": "send_tech",
            "info": GLManager.TechInfo()
        })
        self.send_data_to_all_masters(json_str)

    ## Sends that the salve daemon is alive to all masters available.
    #
    # @param json_obj Not used here.
    # @param connection Not used here.
    # @return None
    def send_alive(self, json_obj, connection):
        json_str = json.JSONEncoder().encode({
            "packet_type": "send_alive",
            "info": GLManager.TechAlive()
        })
        self.send_data_to_all_masters(json_str)

    ## Sends the protocol interface informations to all masters available.
    #
    # @param json_obj Protocol interface informations.
    # @param connection Connection object used to send the response.
    # @return None
    def send_interfaces(self, json_obj, connection):
        try:
            if os.path.exists('/tmp/knxd'):
                call(['service', 'knxd', 'stop'])
            previous_val_knx = self._parser.getValueFromSection(
                'knx', 'interface')
            previous_val_EnOcean = self._parser.getValueFromSection(
                'enocean', 'interface')
            new_val = str(json_obj['interface_arg_knx'])
            self._parser.writeValueFromSection('knx', 'interface', new_val)
            self._parser.writeValueFromSection('knx', 'activated',
                                               str(json_obj['daemon_knx']))
            self._parser.writeValueFromSection(
                'enocean', 'interface', str(json_obj['interface_arg_EnOcean']))
            if not previous_val_knx or previous_val_knx is None:
                call(['update-rc.d', 'knxd', 'defaults'])
                call(['update-rc.d', 'knxd', 'enable'])
            if not new_val or new_val is None:
                Popen(['systemctl', '-q', 'disable', 'knxd'])
            else:
                knx_edit = 'KNXD_OPTS="-e 1.0.254 -D -T -S -b '
                if json_obj['interface_knx'] == 'tpuarts':
                    knx_edit += json_obj[
                        'interface_knx'] + ':/dev/' + new_val + '"'
                else:
                    knx_edit += json_obj['interface_knx'] + ':' + new_val + '"'
                conf_knx = open('/etc/knxd.conf', 'w')
                conf_knx.write(knx_edit + '\n')
                conf_knx.close()
                call(['service', 'knxd', 'start'])
                if json_obj['daemon_knx'] == 1:
                    if os.path.exists('/var/run/monitor_knx.pid'):
                        os.remove('/var/run/monitor_knx.pid')
                    Popen(['monitor_knx', 'ip:localhost', '--daemon'])
        except Exception as e:
            self.logger.error(e)
        json_str = '{"packet_type": "send_interfaces", "aes_pass": "******"}'
        master_hostname = str(json_obj['sender_name'])
        encrypt_IV = AESManager.get_IV()
        spaces = 16 - len(json_str) % 16
        json_str = json_str + (spaces * ' ')
        encode_obj = AES.new(self.private_aes, AES.MODE_CBC, encrypt_IV)
        data = encode_obj.encrypt(json_str)
        connection.send(bytes(encrypt_IV, 'utf-8') + data)
        if previous_val_EnOcean != str(json_obj['interface_arg_EnOcean']):
            call(['service', 'domoslave', 'restart'])

    ## Shuts down the slave D3.
    #
    # @param json_obj Not used here.
    # @param connection Not used here.
    # @return None
    def shutdown_d3(self, json_obj, connection):
        call(['poweroff'])

    ## Reboots the slave D3.
    #
    # @param json_obj Not used here.
    # @param connection Not used here.
    # @return None
    def reboot_d3(self, json_obj, connection):
        call(['reboot'])

    ## Initializes the wifi protocol.
    #
    # @param ssid The SSID of the network.
    # @param password The password to connect to the network.
    # @param security The security type of the connection.
    # @param mode The mode of the initialization of the network interface.
    # @param opt Flag to do some more stuff if it is 1.
    # @return None
    def wifi_init(self, ssid, password, security, mode, opt):
        try:
            ps_process = Popen(["ps", "-x"], stdout=PIPE)
            res = Popen(["grep", "hostapd"],
                        stdin=ps_process.stdout,
                        stdout=PIPE)
            res = res.stdout.read().decode().split("\n")[0].split(' ')
            ps_process.stdout.close()
            if res:
                while ('' in res):
                    res.remove('')
                call(['kill', '-9', res[0]])
            ps_process = Popen(["ps", "-x"], stdout=PIPE)
            res = Popen(["grep", "wpa_supplicant"],
                        stdin=ps_process.stdout,
                        stdout=PIPE)
            res = res.stdout.read().decode().split("\n")[0].split(' ')
            ps_process.stdout.close()
            if res:
                while ('' in res):
                    res.remove('')
                call(['kill', '-9', res[0]])
            call(['ifconfig', 'wlan0', 'down'])
            if mode == WIFI_MODE_DISABLED:
                if opt == 1:
                    call(['service', 'dnsmasq', 'stop'])
            elif mode == WIFI_MODE_CLIENT:
                call(['ifconfig', 'wlan0', 'up'])
                if opt == 1:
                    call(['service', 'dnsmasq', 'stop'])
                conf_file = open('/etc/network/interfaces', 'w')
                conf_str = ''.join([
                    'auto lo\niface lo inet loopback\n\nallow-hotplug eth0\n',
                    'iface eth0 inet dhcp\n\nallow-hotplug usb0\niface usb0 inet dhcp\n\n',
                    'auto wlan0\niface wlan0 inet dhcp\n\twpa-conf ',
                    WPA_SUPPLICANT_CONF_FILE, '\n'
                ])
                conf_file.write(conf_str)
                conf_file.close()
                conf_file = open(WPA_SUPPLICANT_CONF_FILE, 'w')
                conf_str = ''.join([
                    'ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev\n',
                    'update_config=1\nctrl_interface_group=0\neapol_version=1\n',
                    'ap_scan=1\n fast_reauth=1\n\n\nnetwork={\n\tdisabled=0\n',
                    '\tssid="', ssid, '"\n\tscan_ssid=0\n\tpriority=1\n'
                ])
                if security == WIFI_SECURITY_WPA:
                    conf_str += (
                        '\tproto=WPA\n\tkey_mgmt=WPA-PSK\n\tauth_alg=OPEN\n' +
                        '\tpairwise=TKIP CCMP\n\tgroup=TKIP CCMP\n\tpsk="' +
                        password + '"\n')
                elif security == WIFI_SECURITY_WPA2:
                    conf_str += (
                        '\tproto=RSN\n\tkey_mgmt=WPA-PSK\n\tauth_alg=OPEN\n\tpairwise=CCMP TKIP\n'
                        + '\tgroup=CCMP TKIP\n\tpsk="' + password + '"\n')
                elif security == WIFI_SECURITY_WEP:
                    conf_str += '\tkey_mgmt=NONE\n\tauth_alg=SHARED\n'
                    if len(password) == 5 or len(password) == 10:
                        conf_str += '\tgroup=WEP40\n'
                    elif len(password) == 13 or len(password) == 26:
                        conf_str += '\tgroup=WEP104\n'
                    else:
                        conf_str += '\tgroup=WEP40 WEP104\n'
                    conf_str += '\twep_key0="' + password + '"\n\twep_tx_keyidx=0\n'
                conf_str += '\tpriority=1\n}\n'
                conf_file.write(conf_str)
                conf_file.close()
                call([
                    'wpa_supplicant', '-Dnl80211', '-iwlan0',
                    '-c' + WPA_SUPPLICANT_CONF_FILE, '-B'
                ])
                call(['dhclient', 'wlan0'])
            elif mode == WIFI_MODE_ACCESS_POINT:
                call([
                    'ifconfig', 'wlan0', '172.16.0.1', 'netmask',
                    '255.255.255.0', 'up'
                ])
                conf_file = open(HOSTAPD_CONF_FILE, 'w')
                conf_str = ''.join([
                    'interface=wlan0\n\ndriver=nl80211\n\nssid=', ssid, '\n\n',
                    'hw_mode=g\n\nieee80211n=1\n\nchannel=6\n\nbeacon_int=100\n\n',
                    'dtim_period=2\n\nmax_num_sta=255\n\nrts_threshold=2347\n\n',
                    'fragm_threshold=2346\n\nmacaddr_acl=0\n\n'
                ])
                if security == WIFI_SECURITY_WPA:
                    conf_str += ('auth_algs=1\n\nwpa=1\n\nwpa_passphrase=' +
                                 password + '\n\n' +
                                 'wpa_key_mgmt=WPA-PSK\n\nwpa_pairwise=TKIP\n')
                elif security == WIFI_SECURITY_WPA2:
                    conf_str += (
                        'auth_algs=1\n\nwpa=2\n\nwpa_passphrase=' + password +
                        '\n\n' +
                        'wpa_key_mgmt=WPA-PSK\n\nwpa_pairwise=CCMP\n\nrsn_pairwise=CCMP\n'
                    )
                else:
                    self.logger.error('Wifi security = Unknown')
                conf_file.write(conf_str)
                conf_file.close()
                if opt == 1:
                    conf_file = open(DNSMASQ_CONF_FILE, 'w')
                    conf_str = 'domain-needed\ninterface=wlan0\ndhcp-range=172.16.0.2,172.16.0.254,12h\n'
                    conf_file.write(conf_str)
                    conf_file.close()
                    call(['service', 'dnsmasq', 'restart'])
                call([
                    'iptables', '-t', 'nat', '-A', 'POSTROUTING', '-j',
                    'MASQUERADE'
                ])
                call(['hostapd', HOSTAPD_CONF_FILE, '-B'])
            else:
                call(['ifconfig', 'wlan0', 'up'])
                self.logger.error('Wifi mode = Unknown')
        except Exception as e:
            self.logger.error(e)

    ## Updates the wifi informations.
    #
    # @param json_obj JSON Object containing all the informations for the wifi.
    # @param connection Connection object used to send the response.
    # @return None
    def wifi_update(self, json_obj, connection):
        try:
            self._parser.writeValueFromSection('wifi', 'ssid',
                                               json_obj['ssid'])
            self._parser.writeValueFromSection('wifi', 'password',
                                               json_obj['password'])
            self._parser.writeValueFromSection('wifi', 'encryption',
                                               json_obj['security'])
            self._parser.writeValueFromSection('wifi', 'mode',
                                               json_obj['mode'])
            self.wifi_init(json_obj['ssid'], json_obj['password'],
                           json_obj['security'], json_obj['mode'], 1)
        except Exception as e:
            self.logger.error(e)
        json_str = '{"packet_type": "wifi_update", "aes_pass": "******"}'
        master_hostname = str(json_obj['sender_name'])
        encrypt_IV = AESManager.get_IV()
        spaces = 16 - len(json_str) % 16
        json_str = json_str + (spaces * ' ')
        encode_obj = AES.new(self.private_aes, AES.MODE_CBC, encrypt_IV)
        data = encode_obj.encrypt(json_str)
        connection.send(bytes(encrypt_IV, 'utf-8') + data)
Example #9
0
class DeviceManager:
    """
    Device management class. Manages the device described with the ID passed at construction.
    """
    def __init__(self, _id_elem = 0, _option_id = 0, _debug = False):
        self.logger = Logger(LOG_FLAG, LOG_FILE);
        self._id = _id_elem;
        self._debug = _debug;
        self._option_id = _option_id;
        self._parser = DaemonConfigParser(CONF_FILENAME);
        self._db_name = self._parser.getValueFromSection(MYSQL_CONF_SECTION, MYSQL_CONF_USER_ENTRY);
        self._db_passwd = self._parser.getValueFromSection(MYSQL_CONF_SECTION, MYSQL_CONF_PASSWORD_ENTRY);
        self._db_dbname = self._parser.getValueFromSection(MYSQL_CONF_SECTION, MYSQL_CONF_DATABASE_ENTRY);
        self.sql = MasterSql();
        
    ###############################################################
    # Va surement revoir cette fonction et remettre le mode debug #
    # suivant les options qu'il va falloir check ou pas           #
    ###############################################################
    def load_from_db(self):
        """
        Returns the device from the database.
        """
        if self._db_name is None:
            self.logger.error("[ DeviceManager ]: Mysql username not found in '" + CONF_FILENAME + "'");
            return None;
        if self._db_passwd is None:
            self.logger.error("[ DeviceManager ]: Mysql password not found in '" + CONF_FILENAME + "'");
            return None;
        if self._db_dbname is None:
            self.logger.error("[ DeviceManager ]: Mysql database name not found in '" + CONF_FILENAME + "'");
            return None;
        
        db = MysqlHandler(self._db_name, self._db_passwd, self._db_dbname);
        res = db.personnal_query(LOAD_DEVICE_QUERY + str(self._id));
        if len(res) == 0:
            res = db.personnal_query(LOAD_DEVICE_QUERY_IP + str(self._id));
        if len(res) == 0:
            self.logger.error('[ DeviceManager ]: Error: No device with id ' + str(self._id) + ' in database.');
            return None;
        elif len(res) > 1:
            self.logger.error('[ DeviceManager ]: Dunno wut to do if more than one item in DB.');
            return None;
        obj = res[0];
        device = {
            KEY_PROTOCOL_ID: obj[0],
            KEY_DEVICE_ID: obj[1],
            KEY_DAEMON_ID: obj[2],
            KEY_ADDR: obj[3],
            KEY_PLUS_1: obj[4],
            KEY_PLUS_2: obj[5],
            KEY_PLUS_3: obj[6],
            KEY_ROOM_DEVICE_ID: self._id
        };
        db.close();
        db = MysqlHandler(self._db_name, self._db_passwd, self._db_dbname);
        query = CHECK_ROOM_DEVICE_OPTIONS + str(device[KEY_PROTOCOL_ID]) + ' WHERE room_device_id = ' + str(self._id);
        res = db.personnal_query(query);
        db.close();
        if len(res) == 0:
            self.logger.error('[ DeviceManager ]: Error: No room_device_option for room_device_id \'' + str(self._id) + '\'');
            device['option_id'] = self._option_id;
            device['function_id'] = 0;
            device['dpt_id'] = 0;
            if device['protocol_id'] != IP_ID:
                db = MysqlHandler(self._db_name, self._db_passwd, self._db_dbname);
                res = db.personnal_query(GET_DAEMON_FROM_ID + str(device['daemon_id']));
                device['daemon_name'] = res[0][2];
                device['daemon_secretkey'] = res[0][3];
            db.close();
            return device;
        device['addr_dst'] = 0;

        for d in res:
            if d[0] == self._option_id:
                device['addr_dst'] = d[1];
                device['function_id'] = d[3];
                device['dpt_id'] = d[2];
                break;
        device['option_id'] = self._option_id;
        if device['protocol_id'] != IP_ID:
            db = MysqlHandler(self._db_name, self._db_passwd, self._db_dbname);
            res = db.personnal_query(GET_DAEMON_FROM_ID + str(device['daemon_id']));
            device['daemon_name'] = res[0][2];
            device['daemon_secretkey'] = res[0][3];
        db.close();
        return device;
Example #10
0
class MasterDaemon:
    """
    Main class of the master daemon
    It provides communication between master and slave boxes and a part of the database management
    """
    def __init__(self, log_flag):
        self.logger = Logger(log_flag, LOG_FILE);
        self.logger.info('Started Domoleaf Master Daemon');
        self.d3config = {};
        self.aes_slave_keys = {};
        self.aes_master_key = None
        self.connected_clients = {};
        self.sql = MasterSql();
        self._parser = DaemonConfigParser(MASTER_CONF_FILE);
        self.db_username = self._parser.getValueFromSection(MASTER_CONF_MYSQL_SECTION, MASTER_CONF_MYSQL_USER_ENTRY);
        self.db_passwd = self._parser.getValueFromSection(MASTER_CONF_MYSQL_SECTION, MASTER_CONF_MYSQL_PASSWORD_ENTRY);
        self.db_dbname = self._parser.getValueFromSection(MASTER_CONF_MYSQL_SECTION, MASTER_CONF_MYSQL_DB_NAME_ENTRY);
        self.get_aes_slave_keys(0);
        self.reload_camera(None, None, 0);
        self._scanner = Scanner();
        self.hostlist = [];
        self.hostlist.append(Host('', '127.0.0.1', socket.gethostname().upper()));
        self.knx_manager = KNXManager(self.aes_slave_keys);
        self.enocean_manager = EnOceanManager(self.aes_slave_keys);
        self.reload_d3config(None, None, 0);
        self.trigger = Trigger(self);
        self.scenario = Scenario(self);
        self.schedule = Schedule(self);
        self.calcLogs = CalcLogs(self);

        self.functions = {
              1 : self.knx_manager.send_knx_write_short_to_slave,
              2 : self.knx_manager.send_knx_write_long_to_slave,
              3 : self.knx_manager.send_knx_write_speed_fan,
              4 : self.knx_manager.send_knx_write_temp,
              5 : IP_IRManager().send_to_gc,
              6 : self.knx_manager.send_on,
              7 : self.knx_manager.send_to_thermostat,
              8 : self.knx_manager.send_clim_mode,
              9 : HttpReq().http_action,
             10 : self.upnp_audio,
             11 : self.knx_manager.send_knx_write_percent,
             12 : self.knx_manager.send_off,
             13 : self.knx_manager.send_knx_write_short_to_slave_r,
        };
        self.data_function = {
            DATA_MONITOR_KNX                  : self.monitor_knx,
            DATA_MONITOR_IP                   : self.monitor_ip,
            DATA_MONITOR_ENOCEAN              : self.monitor_enocean,
            DATA_MONITOR_BLUETOOTH            : self.monitor_bluetooth,
            DATA_KNX_READ                     : self.knx_read,
            DATA_KNX_WRITE_S                  : self.knx_write_short,
            DATA_KNX_WRITE_L                  : self.knx_write_long,
            DATA_SEND_TO_DEVICE               : self.send_to_device,
            DATA_CRON_UPNP                    : self.cron_upnp,
            DATA_SEND_MAIL                    : self.send_mail,
            DATA_MODIF_DATETIME               : self.modif_datetime,
            DATA_CHECK_SLAVE                  : self.check_slave,
            DATA_RELOAD_CAMERA                : self.reload_camera,
            DATA_RELOAD_D3CONFIG              : self.reload_d3config,
            DATA_BACKUP_DB_CREATE_LOCAL       : self.backup_db_create_local,
            DATA_BACKUP_DB_REMOVE_LOCAL       : self.backup_db_remove_local,
            DATA_BACKUP_DB_LIST_LOCAL         : self.backup_db_list_local,
            DATA_BACKUP_DB_RESTORE_LOCAL      : self.backup_db_restore_local,
            DATA_CHECK_USB                    : self.check_usb,
            DATA_BACKUP_DB_CREATE_USB         : self.backup_db_create_usb,
            DATA_BACKUP_DB_REMOVE_USB         : self.backup_db_remove_usb,
            DATA_BACKUP_DB_LIST_USB           : self.backup_db_list_usb,
            DATA_BACKUP_DB_RESTORE_USB        : self.backup_db_restore_usb,
            DATA_SMARTCMD_LAUNCH              : self.smartcmd_launch,
            DATA_TRIGGERS_LIST_UPDATE         : self.triggers_list_update,
            DATA_SCHEDULES_LIST_UPDATE        : self.schedules_list_update,
            DATA_SCENARIOS_LIST_UPDATE        : self.scenarios_list_update,
            DATA_CHECK_ALL_SCHEDULES          : self.check_schedules,
            DATA_CALC_LOGS                    : self.launch_calc_logs,
            DATA_CHECK_UPDATES                : self.check_updates,
            DATA_UPDATE                       : self.update,
            DATA_SEND_ALIVE                   : self.send_request,
            DATA_SEND_TECH                    : self.send_tech,
            DATA_SEND_INTERFACES              : self.send_interfaces,
            DATA_SHUTDOWN_D3                  : self.shutdown_d3,
            DATA_REBOOT_D3                    : self.reboot_d3,
            DATA_WIFI_UPDATE                  : self.wifi_update,
            DATA_REMOTE_SQL                   : self.remote_sql
        };

    def get_aes_slave_keys(self, db):
        """
        Get the secretkeys of each slave daemon stored in database
        """
        query = "SELECT serial, secretkey FROM daemon";
        res = self.sql.mysql_handler_personnal_query(query, db);
        self_hostname = socket.gethostname();
        for r in res:
            if SLAVE_NAME_PREFIX in r[0] or 'MD3' in r[0]:
                self.aes_slave_keys[r[0]] = r[1];
            elif self_hostname == r[0]:
                self.aes_slave_keys[r[0]] = r[1];
                self.aes_master_key = r[1];

    def stop(self):
        """
        Stops the daemon and closes sockets
        """
        flag = False;
        while not flag:
            flag = True;
            for client in self.connected_clients.values():
                flag = False;
                client.close();
                break;
        self.slave_connection.close();
        sys.exit(0);

    def run(self):
        """
        Initialization of the connections and accepting incomming communications
        """
        self.slave_connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM);
        self.cmd_connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM);
        self.slave_connection.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1);
        self.cmd_connection.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1);
        self.slave_connection.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1);
        self.cmd_connection.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1);
        s_port = self._parser.getValueFromSection(MASTER_CONF_LISTEN_SECTION, MASTER_CONF_LISTEN_PORT_SLAVE_ENTRY);
        c_port = self._parser.getValueFromSection(MASTER_CONF_LISTEN_SECTION, MASTER_CONF_LISTEN_PORT_CMD_ENTRY);
        if not s_port:
            frameinfo = getframeinfo(currentframe());
            self.logger.error('in run: No slave listening port defined in '+MASTER_CONF_FILE);
            sys.exit(1);
        if not c_port:
            frameinfo = getframeinfo(currentframe());
            self.logger.error('in run: No command listening port defined in '+MASTER_CONF_FILE);
            sys.exit(1);
        self.slave_connection.bind(('', int(s_port)));
        self.slave_connection.listen(MAX_SLAVES);
        self.cmd_connection.bind(('', int(c_port)));
        self.cmd_connection.listen(MAX_CMDS);
        self.loop();

    def loop(self):
        """
        Main loop. Waits for new connections.
        """
        self.run = True;
        while self.run:
            try:
                rlist, wlist, elist = select.select([self.slave_connection], [], [], SELECT_TIMEOUT);
                for connection in rlist:
                    self.accept_new_slave_connection(connection);
                rlist, wlist, elist = select.select([self.cmd_connection], [], [], SELECT_TIMEOUT);
                for connection in rlist:
                    self.accept_new_cmd_connection(connection);
            except KeyboardInterrupt as e:
                frameinfo = getframeinfo(currentframe());
                self.logger.info('in loop: Keyboard interrupt: leaving program');
                print("[ MASTER DAEMON ",frameinfo.filename,":",str(frameinfo.lineno)," ]: Keyboard Interrupt");
                self.stop();
                sys.exit(0);
            except ValueError as e:
                frameinfo = getframeinfo(currentframe());
                self.logger.error('in loop: Value error: '+str(e));
                print("[ MASTER DAEMON ",frameinfo.filename,":",str(frameinfo.lineno),"]: Value Error");
                print(e);
                pass;

    def accept_new_cmd_connection(self, connection):
        """
        Gets new domoleaf connections and threads the treatment.
        """
        new_connection, addr = connection.accept();
        r = CommandReceiver(new_connection, self);
        r.start();

    def accept_new_slave_connection(self, connection):
        """
        Gets new slave connections and threads the treatment.
        """
        new_connection, addr = connection.accept();
        myname = socket.gethostname();
        try:
            name = socket.gethostbyaddr(addr[0])[0]
        except socket.error as serr:
            name = 'localhost'
        if name == 'localhost':
            name = myname
        name = name.split('.')[0];
        r = SlaveReceiver(new_connection, name, self);
        r.start();

    def parse_data(self, data, connection, daemon_id, db):
        """
        Once data are received whether from domoleaf or slave, the function of the packet_type in data is called.
        """
        json_obj = json.JSONDecoder().decode(data);
        json_obj['daemon_id'] = daemon_id;
        if json_obj['packet_type'] in self.data_function.keys():
            self.data_function[json_obj['packet_type']](json_obj, connection, db);
        else:
            frameinfo = getframeinfo(currentframe());

    def check_updates(self, json_obj, connection, db):
        query = 'SELECT configuration_value FROM configuration WHERE configuration_id=4';
        actual_version = self.sql.mysql_handler_personnal_query(query, db);
        if not actual_version:
            self.logger.error("CHECK_UPDATE : No Master Version");
            return;
        query = 'UPDATE configuration SET configuration_value="" WHERE configuration_id=13';
        self.sql.mysql_handler_personnal_query(query, db);
        p = call(['dpkg', '--configure', '-a'])
        p = Popen(['apt-get', 'update'], stdin=PIPE, stdout=PIPE, stderr=PIPE, bufsize=-1);
        output, error = p.communicate();
        p = Popen(['apt-show-versions',  '-u', 'domomaster'], stdin=PIPE, stdout=PIPE, stderr=PIPE, bufsize=-1);
        output, error = p.communicate();
        if not p.returncode:
            tab = output.decode("utf-8").split(" ");
            version = tab[-1].rsplit("\n")[0];
        else:
            version = actual_version[0][0];
        query = ''.join(['UPDATE configuration SET configuration_value="', version, '" WHERE configuration_id=13']);
        self.sql.mysql_handler_personnal_query(query, db);

    def update(self, json_obj, connection, db):
        call(['apt-get', 'update']);
        p = Popen("DEBIAN_FRONTEND=noninteractive apt-get install domomaster domoslave -y ",
              shell=True, stdin=None, stdout=False, stderr=False,executable="/bin/bash");
        output, error = p.communicate();
        hostname = socket.gethostname();
        if '.' in hostname:
            hostname = hostname.split('.')[0];
        version = os.popen("dpkg-query -W -f='${Version}\n' domomaster").read().split('\n')[0];
        query = ''.join(['UPDATmon SET version="', version, '" WHERE name="', hostname, '"' ]);
        self.sql.mysql_handler_personnal_query(query, db);
        query = ''.join(['UPDATE configuration SET configuration_value="', version, '" WHERE configuration_id=4']);
        self.sql.mysql_handler_personnal_query(query, db);
        json_obj['data'].append(hostname);
        port = self._parser.getValueFromSection('connect', 'port');
        for host in self.hostlist:
            if (host._Hostname.startswith('MD3') or host._Hostname.startswith('SD3')) and host._Hostname not in json_obj['data']:
                sock = socket.create_connection((host._IpAddr, port));
                json_str = json.JSONEncoder().encode(json_obj);
                sock.send(bytes(json_str, 'utf-8'));
                data = sock.recv(4096);
                decrypt_IV = data[:16].decode();
                decode_obj = AES.new(self.aes_master_key, AES.MODE_CBC, decrypt_IV);
                data2 = decode_obj.decrypt(data[16:]).decode();
                version = data2['new_version'];
                query = ''.join(['UPDATE daemon SET version="', version, '" WHERE name="', host._Hostname, '"']);
                self.sql.mysql_handler_personnal_query(query, db);
                sock.close();

    def backup_db_create_local(self, json_obj, connection, db):
        path = '/etc/domoleaf/sql/backup/';
        filename = 'domoleaf_backup_';
        t = str(time.time());
        if '.' in t:
            t = t.split('.')[0];
        filename += t+'.sql';
        os.system("mysqldump --defaults-file=/etc/mysql/debian.cnf domoleaf > "+path+filename);
        os.system('cd '+path+' && tar -czf '+filename+'.tar.gz'+' '+filename);
        os.system('rm '+path+filename);

    def backup_db_remove_local(self, json_obj, connection, db):
        filename = ''.join(['/etc/domoleaf/sql/backup/domoleaf_backup_', str(json_obj['data']), '.sql.tar.gz']);
        if str(json_obj['data'][0]) == '.' or str(json_obj['data'][0]) == '/':
            self.logger.error('The filename is corrupted. Aborting database file removing.')
            return;
        try:
            os.stat(filename);
        except Exception as e:
            try:
                filename = filename.split('.tar.gz')[0];
                os.stat(filename);
            except Exception as e:
                self.logger.error("The database file to remove does not exists.")
                self.logger.error(e)
                return;
        os.remove(filename);

    def backup_db_list_local(self, json_obj, connection, db):
        json_obj = [];
        append = json_obj.append;
        backup_list = os.listdir('/etc/domoleaf/sql/backup/')
        for f in backup_list:
            s = os.stat('/etc/domoleaf/sql/backup/'+f);
            if '.sql' in f:
                g = f.split('.sql')[0];
                append({"name": g, "size": s.st_size});
        json_sorted = sorted(json_obj, key=lambda json_obj: json_obj['name'], reverse=True);
        json_str = json.JSONEncoder().encode(json_sorted);
        connection.send(bytes(json_str, 'utf-8'));

    def backup_db_restore_local(self, json_obj, connection, db):
        path = '/etc/domoleaf/sql/backup/';
        filename = ''.join(['domoleaf_backup_', str(json_obj['data']), '.sql.tar.gz']);
        if json_obj['data'][0] == '.' or json_obj['data'][0] == '/':
            self.logger.error('The filename is corrupted. Aborting database restoring.')
            return;
        try:
            os.stat(path+filename);
            os.system('cd '+path+' && tar -xzf '+filename);
            os.system('mysql --defaults-file=/etc/mysql/debian.cnf domoleaf < '+path+filename.split('.tar.gz')[0]);
            os.system('rm '+path+filename.split('.tar.gz')[0]);
            return;
        except Exception as e:
            try:
                filename = filename.split('.tar.gz')[0];
                os.stat(path+filename);
            except Exception as e:
                self.logger.error("The database file to restore does not exists.");
                self.logger.error(e);
                return;
        os.system('mysql --defaults-file=/etc/mysql/debian.cnf domoleaf < '+path+filename);

    def check_usb(self, json_obj, connection, db):
        try:
            sdx1 = glob.glob('/dev/sd?1')[0];
        except Exception as e:
            return;
        if not (os.path.exists(sdx1)):
            json_obj = 0;
        else:
            json_obj = 1;
        json_str = json.JSONEncoder().encode(json_obj);
        connection.send(bytes(json_str, 'utf-8'));

    def backup_db_list_usb(self, json_obj, connection, db):
        json_obj = [];
        append = json_obj.append
        sdx1 = glob.glob('/dev/sd?1')[0];
        if not (os.path.exists(sdx1)):
            return;
        os.system('mount '+sdx1+' /etc/domoleaf/mnt');
        os.system('mkdir -p /etc/domoleaf/mnt/backup');
        backup_list = os.listdir('/etc/domoleaf/mnt/backup/')
        for f in backup_list:
            s = os.stat('/etc/domoleaf/mnt/backup/'+f);
            if '.sql' in f:
                g = f.split('.sql')[0];
                append({"name": g, "size": s.st_size});
        os.system('umount /etc/domoleaf/mnt');
        json_sorted = sorted(json_obj, key=lambda json_obj: json_obj['name'], reverse=True);
        json_str = json.JSONEncoder().encode(json_sorted);
        connection.send(bytes(json_str, 'utf-8'));

    def backup_db_remove_usb(self, json_obj, connection, db):
        filename = ''.join(['/etc/domoleaf/mnt/backup/domoleaf_backup_', str(json_obj['data']), '.sql.tar.gz']);
        if str(json_obj['data'][0]) == '.' or str(json_obj['data'][0]) == '/':
            self.logger.error('The filename is corrupted. Aborting database file removing.')
            return;
        sdx1 = glob.glob('/dev/sd?1')[0];
        if not (os.path.exists(sdx1)):
            return;
        os.system('mount '+sdx1+' /etc/domoleaf/mnt');
        path = '/etc/domoleaf/mnt/backup/';
        try:
            os.stat(filename);
        except Exception as e:
            try:
                filename = filename.split('.tar.gz')[0];
                os.stat(filename);
            except Exception as e:
                self.logger.error("The database file to remove does not exists.")
                self.logger.error(e)
                os.system('umount /etc/domoleaf/mnt');
                return;
        os.remove(filename);
        os.system('umount /etc/domoleaf/mnt');

    def backup_db_restore_usb(self, json_obj, connection, db):
        path = '/etc/domoleaf/mnt/backup/';
        filename = ''.join(['domoleaf_backup_', str(json_obj['data']), '.sql']);
        if json_obj['data'][0] == '.' or json_obj['data'][0] == '/':
            self.logger.error('The filename is corrupted. Aborting database restoring.')
            return;
        sdx1 = glob.glob('/dev/sd?1')[0];
        if not (os.path.exists(sdx1)):
            return;
        os.system('mount '+sdx1+' /etc/domoleaf/mnt');
        try:
            os.stat(path+filename);
            os.system('cp '+path+filename+' /tmp/ && umount /etc/domoleaf/mnt && cd /tmp/');
            os.system('mysql --defaults-file=/etc/mysql/debian.cnf domoleaf < /tmp/'+filename);
            os.remove('/tmp/'+filename);
            return;
        except Exception as e:
            try:
                filename += '.tar.gz';
                os.stat(path+filename);
                os.system('cp '+path+filename+' /tmp/ && umount /etc/domoleaf/mnt && cd /tmp/ && tar -xzf '+filename);
            except Exception as e:
                self.logger.error("The database file to restore does not exists.");
                self.logger.error(e);
                os.system('umount /etc/domoleaf/mnt');
                return;
        os.system('umount /etc/domoleaf/mnt');
        os.system('mysql --defaults-file=/etc/mysql/debian.cnf domoleaf < /tmp/'+filename.split('.tar.gz')[0]);
        os.remove('/tmp/'+filename);
        os.remove('/tmp/'+filename.split('.tar.gz')[0]);

    def backup_db_create_usb(self, json_obj, connection, db):
        sdx1 = glob.glob('/dev/sd?1')[0];
        if not (os.path.exists(sdx1)):
            return;
        os.system('mount '+sdx1+' /etc/domoleaf/mnt');
        path = '/etc/domoleaf/mnt/backup/';
        filename = 'domoleaf_backup_';
        os.system('mkdir -p '+path);
        t = str(time.time());
        if '.' in t:
            t = t.split('.')[0];
        filename += t+'.sql';
        os.system("mysqldump --defaults-file=/etc/mysql/debian.cnf domoleaf > "+path+filename);
        os.system('cd '+path+' && tar -czf '+filename+'.tar.gz'+' '+filename);
        os.system('rm '+path +filename);
        os.system('umount /etc/domoleaf/mnt');

    def monitor_knx(self, json_obj, connection, db):
        """
        Callback called each time a monitor_knx packet is received.
        Updates room_device_option values in the database and check scenarios.
        """
        daemon_id = self.sql.update_knx_log(json_obj, db);
        doList = self.knx_manager.update_room_device_option(daemon_id, json_obj, db);
        if doList:
            self.scenario.setValues(self.get_global_state(db), self.trigger, self.schedule, connection, doList);
            self.scenario.start();
        connection.close();

    def knx_write_short(self, json_obj, connection, db):
        """
        Callback called each time a knx_write_short packet is received.
        Updates room_device_option values in the database.
        """
        daemons = self.sql.get_daemons(db);
        slave_name = self.get_slave_name(json_obj, daemons);
        if slave_name is None:
            connection.close();
            return None;
        dev = {}
        dev["addr_dst"] = json_obj['data']['addr']
        slave_name = slave_name.split('.')[0];
        self.knx_manager.send_knx_write_short_to_slave(json_obj, dev, slave_name);
        connection.close();
        return None;

    def knx_write_long(self, json_obj, connection, db):
        """
        Callback called each time a knx_write_long packet is received.
        Updates room_device_option values in the database.
        """
        daemons = self.sql.get_daemons(db);
        slave_name = self.get_slave_name(json_obj, daemons);
        if slave_name is None:
            connection.close();
            return None;
        dev = {}
        dev["addr_dst"] = json_obj['data']['addr']
        slave_name = slave_name.split('.')[0];
        self.knx_manager.send_knx_write_long_to_slave(json_obj, dev, slave_name);
        connection.close();
        return None;

    def knx_read(self, json_obj, connection, db):
        """
        Callback called each time a knx_read packet is received.
        """
        daemons = self.sql.get_daemons(db);
        slave_name = self.get_slave_name(json_obj, daemons);
        if slave_name is None:
            return None;
        slave_name = slave_name.split('.')[0];
        self.knx_manager.send_knx_read_request_to_slave(slave_name, json_obj);
        connection.close();

    def monitor_ip(self, json_obj, connection, db):
        """
        Callback called each time a monitor_ip packet is received.
        A new local network scan is performed and the result stored in the database
        """
        self.scanner.scan();
        self.sql.insert_hostlist_in_db(self.scanner._HostList, db);
        self.hostlist = self.scanner._HostList;
        connection.close();

    def monitor_bluetooth(self, json_obj, connection, db):
        """
        TODO
        """
        connection.close();
        return None;

    def monitor_enocean(self, json_obj, connection, db):
        """
        Callback called each time a monitor_enocean packet is received.
        Stores the data in enocean_log table.
        """
        daemon_id = self.sql.update_enocean_log(json_obj, db);
        doList = self.enocean_manager.update_room_device_option(daemon_id, json_obj, db);
        connection.close();
        if doList:
            self.scenario.setValues(self.get_global_state(db), self.trigger, self.schedule, connection, doList);
            self.scenario.start();
        return None;

    def send_to_device(self, json_obj, connection, db):
        """
        Retrieves the good device in the database and builds the request to send.
        """
        hostname = '';
        dm = DeviceManager(int(json_obj['data']['room_device_id']), int(json_obj['data']['option_id']), DEBUG_MODE);
        dev = dm.load_from_db(db);
        if dev is None:
            connection.close();
            return ;
        if 'daemon_name' in dev:
            for host in self.hostlist:
                if dev['daemon_name'] == host._Hostname:
                    hostname = host._Hostname;
                    break;
        function_writing = int(dev['function_writing']);
        if (function_writing > 0):
            try:
                self.functions[function_writing](json_obj, dev, hostname);
            except Exception as e:
                self.logger.error(e);
        connection.close();

    def upnp_audio(self, json_obj, dev, hostname):
        cmd = UpnpAudio(dev['addr'], int(dev['plus1']));
        cmd.action(json_obj);

    def get_ip_ifname(self, ifname):
        """
        Retrieves network interface name from IP address.
        """
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM);
        try:
            res = socket.inet_ntoa(fcntl.ioctl(s.fileno(),
                                               0x8915,
                                               struct.pack('256s', bytes(ifname, 'utf-8')))[20:24]);
            return res;
        except Exception as e:
            frameinfo = getframeinfo(currentframe());
            self.logger.error('in get_ip_ifname: '+str(e));
            return None;

    def cron_upnp(self, json_obj, connection, db):
        """
        Callback called each time a cron_upnp packet is received.
        """
        local_ip = self.get_ip_ifname("eth0");
        if local_ip is None:
            connection.close();
            return None;
        query = "SELECT configuration_id, configuration_value FROM configuration";
        res = self.sql.mysql_handler_personnal_query(query);
        actions = json_obj['data'];
        for act in actions:
            if act['action'] == 'open':
                for r in res:
                    if int(r[0]) == int(act['configuration_id']):
                        if int(r[0]) == 1:
                            call(["upnpc", "-a", local_ip, str(r[1]), "80", act['protocol']]);
                        elif int(r[0]) == 2:
                            call(["upnpc", "-a", local_ip, str(r[1]), "443", act['protocol']]);
            elif act['action'] == 'close':
                for r in res:
                    if int(r[0]) == int(act['configuration_id']):
                        call(["upnpc", "-d", str(r[1]), act['protocol']]);

    def reload_camera(self, json_obj, connection, db):
        """
        Generation of the file devices.conf located in /etc/domoleaf by default.
        """
        camera_file = open(CAMERA_CONF_FILE, 'w');
        query = "SELECT room_device_id, addr, plus1 FROM room_device WHERE protocol_id = 6";
        res = self.sql.mysql_handler_personnal_query(query, db);
        for r in res:
            ip = str(r[1]);
            if r[1] and utils.is_valid_ip(ip):
                camera_file.write("location /device/"+str(r[0]));
                camera_file.write("/ {\n")
                camera_file.write("\tproxy_buffering off;\n")
                camera_file.write("\tproxy_pass http://"+ip);
                if str(r[2]).isdigit():
                    camera_file.write(":"+str(r[2])+"/;\n}\n\n");
                else:
                    camera_file.write(":/;\n}\n\n");
        camera_file.close();
        call(["service", "nginx", "restart"]);

    def reload_d3config(self, json_obj, connection, db):
        """
        Loads port config. Reading in database and storing.
        """
        query = "SELECT configuration_id, configuration_value FROM configuration";
        res = self.sql.mysql_handler_personnal_query(query, db);
        for r in res:
            self.d3config[str(r[0])] = r[1];

    def check_slave(self, json_obj, connection, db):
        """
        Asks "check_slave" to the slave described in json_obj and waits for answer.
        """
        query = ''.join(["SELECT serial, secretkey FROM daemon WHERE daemon_id=", str(json_obj['data']['daemon_id'])]);
        res = self.sql.mysql_handler_personnal_query(query, db);
        if res is None or not res:
            self.logger.error('in check_slave: No daemon for id '+str(json_obj['data']['daemon_id']));
            connection.close();
            return ;
        elif len(res) > 1:
            self.logger.error('in check_slave: Too much daemons for id '+str(json_obj['data']['daemon_id']));
            connection.close();
            return ;
        hostname = res[0][0];
        self_hostname = socket.gethostname();
        if hostname == self_hostname:
            ip = '127.0.0.1';
        else:
            ip = '';
            for h in self.hostlist:
                if hostname in h._Hostname.upper():
                    ip = h._IpAddr;
        if not ip:
            self.logger.error('in check_slave: '+hostname+' not in hostlist. Try perform network scan again.');
            connection.close();
            return ;
        port = self._parser.getValueFromSection('connect', 'port');
        sock = socket.create_connection((ip, port));
        if '.' in self_hostname:
            self_hostname = self_hostname.split('.')[0];
        aes_IV = AESManager.get_IV();
        aes_key = self.get_secret_key(hostname);
        obj_to_send = ''.join(['{"packet_type": "check_slave", "sender_name": "', self_hostname, '"}']);
        encode_obj = AES.new(aes_key, AES.MODE_CBC, aes_IV);
        spaces = 16 - len(obj_to_send) % 16;
        sock.send(bytes(aes_IV, 'utf-8') + encode_obj.encrypt(obj_to_send + (spaces * ' ')));
        rlist, wlist, elist = select.select([sock], [], [], SELECT_TIMEOUT * 10);
        val = '0';
        version = '';
        interface_knx = '';
        interface_enocean = '';
        data = sock.recv(4096);
        if data:
            decrypt_IV = data[:16].decode();
            decode_obj = AES.new(res[0][1], AES.MODE_CBC, decrypt_IV);
            data2 = decode_obj.decrypt(data[16:]).decode();
            resp = json.JSONDecoder().decode(data2);
            if str(self.aes_slave_keys[hostname]) == str(resp['aes_pass']):
                val = '1';
                version = resp['version'];
                interface_knx = resp['interface_knx'];
                interface_enocean = resp['interface_enocean'];
            connection.send(bytes(version, 'utf-8'));
        connection.close();
        query = ''.join(['UPDATE daemon SET validation=', val, ', version="', version, '" WHERE serial="', hostname, '"']);
        self.sql.mysql_handler_personnal_query(query, db);
        query = ''.join(['UPDATE daemon_protocol SET interface="', interface_knx, '" WHERE daemon_id="', str(json_obj['data']['daemon_id']), '" AND protocol_id="1"']);
        self.sql.mysql_handler_personnal_query(query, db);
        query = ''.join(['UPDATE daemon_protocol SET interface="', interface_enocean, '" WHERE daemon_id="', str(json_obj['data']['daemon_id']), '" AND protocol_id="2"']);
        self.sql.mysql_handler_personnal_query(query, db);
        sock.close();

    def get_secret_key(self, hostname):
        """
        Retrieves the secretkey of 'hostname' in the database.
        """
        query = ''.join(['SELECT serial, secretkey FROM daemon WHERE serial = \'', hostname, '\'']);
        res = self.sql.mysql_handler_personnal_query(query);
        for r in res:
            if r[0] == hostname:
                return str(r[1]);

    def send_mail(self, json_obj, connection, db):
        """
        Callback called each time a send_mail packet is received.
        The parameters are stored in 'json_obj'.
        """
        try:
            from_addr = formataddr((self.d3config['6'], self.d3config['5']));
            host = self.d3config['7'];
            secure = self.d3config['8']
            port = self.d3config['9'];
            username = self.d3config['10'];
            password = self.d3config['11'];
            msg = MIMEMultipart();
            mdr = json_obj['data']['object'];
            msg['Subject'] = json_obj['data']['object'];
            msg['From'] = from_addr;
            msg['To'] = json_obj['data']['destinator'];
            msg.attach(MIMEText(json_obj['data']['message']));
            server = smtplib.SMTP(host, port);
            if (secure == 2):
                server.ehlo();
                server.starttls();
                server.ehlo();
            if not username and not password:
                server.login(self.d3config['5'], username);
            server.sendmail(from_addr, json_obj['data']['destinator'], msg.as_string());
            server.quit();
            connection.close();
        except Exception as e:
            self.logger.error('Error for sending mail');
            self.logger.error(e);
            connection.send(bytes('Error', 'utf-8'));
            connection.close();

    def modif_datetime(self, json_obj, connection, db):
        os.system('date --set '+json_obj['data'][0]);
        os.system('date --set '+json_obj['data'][1]);

    def get_slave_name(self, json_obj, daemons):
        """
        Retrieves the hostname of the daemon described by 'json_obj' in the 'daemons' list.
        """
        daemon_found = False;
        slave_name = '';
        for d in daemons:
            if int(json_obj['data']['daemon']) == int(d[0]):
                daemon_found = True;
                slave_name = str(d[2]);
                break;
        if daemon_found is False:
            frameinfo = getframeinfo(currentframe());
            self.logger.error('in get_slave_name: '+str(json_obj['data']['daemon']));
            return None;
        if str(json_obj['data']['addr']).count('/') != 2:
            frameinfo = getframeinfo(currentframe());
            self.logger.error('in get_slave_name: '+str(json_obj['data']['addr']));
            return None;
        return slave_name;

    def reload_web_server(self):
        """
        Call "service reload nginx"
        """
        self.logger.debug('Reloading web server...');
        call(["service", "nginx", "reload"]);
        self.logger.debug('[ OK ] Done reloading web server.');

    def smartcmd_launch(self, json_obj, connection, db):
        s = Smartcommand(self, int(json_obj['data']))
        s.setValues(connection);
        s.start();

    def triggers_list_update(self, json_obj, connection, db):
        self.trigger.update_triggers_list(db);

    def schedules_list_update(self, json_obj, connection, db):
        self.schedule.update_schedules_list(db);

    def scenarios_list_update(self, json_obj, connection, db):
        self.scenario.update_scenarios_list(db);

    def check_schedules(self, json_obj, connection, db):
        self.schedule.check_all_schedules(connection);

    def launch_calc_logs(self, json_obj, connection, db):
        try:
            self.calcLogs.sort_logs(connection, db);
        except Exception as e:
            self.logger.error(e);

    def get_global_state(self, db):
        query = 'SELECT room_device_id, option_id, opt_value FROM room_device_option';
        res = self.sql.mysql_handler_personnal_query(query, db);
        filtered = [];
        append = filtered.append;
        for elem in res:
            if elem[2]:
                append(elem);
        global_state = [];
        if filtered:
            global_state = filtered;
        else:
            global_state = '';
        return global_state;

    def send_tech(self, json_obj, connection, db):
        query = 'SELECT configuration_value FROM configuration WHERE configuration_id=1';
        http = self.sql.mysql_handler_personnal_query(query, db);
        query = 'SELECT configuration_value FROM configuration WHERE configuration_id=2';
        ssl = self.sql.mysql_handler_personnal_query(query, db);
        json_obj['info']['http'] = http[0][0];
        json_obj['info']['ssl']  = ssl[0][0];
        self.send_request(json_obj, connection, db)

    def send_request(self, json_obj, connection, db):
        if self._parser.getValueFromSection('greenleaf', 'commercial') == "1":
            admin_addr = self._parser.getValueFromSection('greenleaf', 'admin_addr')
            hostname = socket.gethostname()
            GLManager.SendRequest(str(json_obj), admin_addr, self.get_secret_key(hostname))

    def send_interfaces(self, json_obj, connection, db):
        query = ''.join(["SELECT serial, secretkey FROM daemon WHERE daemon_id=", str(json_obj['data']['daemon_id'])]);
        res = self.sql.mysql_handler_personnal_query(query, db);
        if res is None or not res:
            self.logger.error('in send_interfaces: No daemon for id '+str(json_obj['data']['daemon_id']));
            connection.close();
            return ;
        elif len(res) > 1:
            self.logger.error('in send_interfaces: Too much daemons for id '+str(json_obj['data']['daemon_id']));
            connection.close();
            return ;
        hostname = res[0][0];
        ip = '';
        for h in self.hostlist:
            if hostname in h._Hostname.upper():
                ip = h._IpAddr;
        if not ip:
            self.logger.error('in send_interfaces: '+hostname+' not in hostlist. Try perform network scan again.');
            connection.close();
            return ;
        port = self._parser.getValueFromSection('connect', 'port');
        sock = socket.create_connection((ip, port));
        self_hostname = socket.gethostname();
        if '.' in self_hostname:
            self_hostname = self_hostname.split('.')[0];
        aes_IV = AESManager.get_IV();
        aes_key = self.get_secret_key(hostname);
        obj_to_send = json.JSONEncoder().encode(
            {
                "packet_type": "send_interfaces", 
                "sender_name": self_hostname,
                "interface_knx": json_obj['data']['interface_knx'],
                "interface_EnOcean": json_obj['data']['interface_EnOcean'],
                "interface_arg_knx": json_obj['data']['interface_arg_knx'],
                "interface_arg_EnOcean": json_obj['data']['interface_arg_EnOcean'],
                "daemon_knx": json_obj['data']['daemon_knx']
            }
        );
        encode_obj = AES.new(aes_key, AES.MODE_CBC, aes_IV);
        spaces = 16 - len(obj_to_send) % 16;
        sock.send(bytes(aes_IV, 'utf-8') + encode_obj.encrypt(obj_to_send + (spaces * ' ')));
        rlist, wlist, elist = select.select([sock], [], [], SELECT_TIMEOUT * 300);
        re = '';
        data = sock.recv(4096);
        if data:
            decrypt_IV = data[:16].decode();
            host = None;
            for h in self.hostlist:
                if h._IpAddr == ip:
                    host = h;
            decode_obj = AES.new(res[0][1], AES.MODE_CBC, decrypt_IV);
            data2 = decode_obj.decrypt(data[16:]).decode();
            resp = json.JSONDecoder().decode(data2);
            hostname = host._Hostname;
            if '.' in host._Hostname:
                hostname = host._Hostname.split('.')[0];
            if str(self.aes_slave_keys[hostname]) == str(resp['aes_pass']):
                re = '1';
            connection.send(bytes(re, 'utf-8'));
        connection.close();
        sock.close();

    def shutdown_d3(self, json_obj, connection, db):
        """
        Asks "shutdown_d3" to the slave described in json_obj for shutdown daemon.
        """
        query = ''.join(["SELECT serial, secretkey FROM daemon WHERE daemon_id=", str(json_obj['data']['daemon_id'])]);
        res = self.sql.mysql_handler_personnal_query(query, db);
        if res is None or not res:
            self.logger.error('in shutdown_d3: No daemon for id '+str(json_obj['data']['daemon_id']));
            connection.close();
            return ;
        elif len(res) > 1:
            self.logger.error('in shutdown_d3: Too much daemons for id '+str(json_obj['data']['daemon_id']));
            connection.close();
            return ;
        hostname = res[0][0];
        ip = '';
        for h in self.hostlist:
            if hostname in h._Hostname.upper():
                ip = h._IpAddr;
        if not ip:
            self.logger.error('in shutdown_d3: '+hostname+' not in hostlist. Try perform network scan again.');
            connection.close();
            return ;
        port = self._parser.getValueFromSection('connect', 'port');
        sock = socket.create_connection((ip, port));
        self_hostname = socket.gethostname();
        if '.' in self_hostname:
            self_hostname = self_hostname.split('.')[0];
        aes_IV = AESManager.get_IV();
        aes_key = self.get_secret_key(hostname);
        obj_to_send = ''.join(['{"packet_type": "shutdown_d3", "sender_name": "', self_hostname, '"}']);
        encode_obj = AES.new(aes_key, AES.MODE_CBC, aes_IV);
        spaces = 16 - len(obj_to_send) % 16;
        sock.send(bytes(aes_IV, 'utf-8') + encode_obj.encrypt(obj_to_send + (spaces * ' ')));
        connection.close();
        sock.close();

    def reboot_d3(self, json_obj, connection, db):
        """
        Asks "reboot_d3" to the slave described in json_obj for reboot daemon.
        """
        query = ''.join(["SELECT serial, secretkey FROM daemon WHERE daemon_id=", str(json_obj['data']['daemon_id'])]);
        res = self.sql.mysql_handler_personnal_query(query, db);
        if res is None or not res:
            self.logger.error('in reboot_d3: No daemon for id '+str(json_obj['data']['daemon_id']));
            connection.close();
            return ;
        elif len(res) > 1:
            self.logger.error('in reboot_d3: Too much daemons for id '+str(json_obj['data']['daemon_id']));
            connection.close();
            return ;
        hostname = res[0][0];
        ip = '';
        for h in self.hostlist:
            if hostname in h._Hostname.upper():
                ip = h._IpAddr;
        if not ip:
            self.logger.error('in reboot_d3: '+hostname+' not in hostlist. Try perform network scan again.');
            connection.close();
            return ;
        port = self._parser.getValueFromSection('connect', 'port');
        sock = socket.create_connection((ip, port));
        self_hostname = socket.gethostname();
        if '.' in self_hostname:
            self_hostname = self_hostname.split('.')[0];
        aes_IV = AESManager.get_IV();
        aes_key = self.get_secret_key(hostname);
        obj_to_send = ''.join(['{"packet_type": "reboot_d3", "sender_name": "', self_hostname, '"}']);
        encode_obj = AES.new(aes_key, AES.MODE_CBC, aes_IV);
        spaces = 16 - len(obj_to_send) % 16;
        sock.send(bytes(aes_IV, 'utf-8') + encode_obj.encrypt(obj_to_send + (spaces * ' ')));
        connection.close();
        sock.close();

    def wifi_update(self, json_obj, connection, db):
        """
        Send "wifi_update" to the slave described in json_obj for update the wifi configuration.
        """
        query = ''.join(["SELECT serial, secretkey FROM daemon WHERE daemon_id=", str(json_obj['data']['daemon_id'])]);
        res = self.sql.mysql_handler_personnal_query(query, db);
        if res is None or not res:
            self.logger.error('in wifi_update: No daemon for id '+str(json_obj['data']['daemon_id']));
            connection.close();
            return ;
        elif len(res) > 1:
            self.logger.error('in wifi_update: Too much daemons for id '+str(json_obj['data']['daemon_id']));
            connection.close();
            return ;
        hostname = res[0][0];
        ip = '';
        for h in self.hostlist:
            if hostname in h._Hostname.upper():
                ip = h._IpAddr;
        if not ip:
            self.logger.error('in wifi_update: '+hostname+' not in hostlist. Try perform network scan again.');
            connection.close();
            return ;
        port = self._parser.getValueFromSection('connect', 'port');
        sock = socket.create_connection((ip, port));
        self_hostname = socket.gethostname();
        if '.' in self_hostname:
            self_hostname = self_hostname.split('.')[0];
        aes_IV = AESManager.get_IV();
        aes_key = self.get_secret_key(hostname);
        obj_to_send = ''.join(['{"packet_type": "wifi_update", "sender_name": "', str(self_hostname),
              '", "ssid": "', str(json_obj['data']['ssid']), '", "password": "******", "security": "', str(json_obj['data']['security']),
              '", "mode": "', str(json_obj['data']['mode']), '"}']);
        encode_obj = AES.new(aes_key, AES.MODE_CBC, aes_IV);
        spaces = 16 - len(obj_to_send) % 16;
        sock.send(bytes(aes_IV, 'utf-8') + encode_obj.encrypt(obj_to_send + (spaces * ' ')));
        rlist, wlist, elist = select.select([sock], [], [], SELECT_TIMEOUT * 300);
        re = '';
        for s in rlist:
            data = sock.recv(4096);
            if not data:
                continue;
            decrypt_IV = data[:16].decode();
            host = None;
            for h in self.hostlist:
                if h._IpAddr == ip:
                    host = h;
            decode_obj = AES.new(res[0][1], AES.MODE_CBC, decrypt_IV);
            data2 = decode_obj.decrypt(data[16:]).decode();
            resp = json.JSONDecoder().decode(data2);
            hostname = host._Hostname;
            if '.' in host._Hostname:
                hostname = host._Hostname.split('.')[0];
            if str(self.aes_slave_keys[hostname]) == str(resp['aes_pass']):
                re = '1';
            connection.send(bytes(re, 'utf-8'));
        connection.close();
        sock.close();
    
    def remote_sql(self, json_obj, connection):
        """
        Execute sql command from configurator
        """
        db = MasterSql();
        req = json_obj['data'].split(';');
        for item in req:
            if item != '':
                db.mysql_handler_personnal_query(item);
        connection.close();
        return;
Example #11
0
class KNXManager:
    """
    KNX management class
    """
    def __init__(self, slave_keys):
        self.knx_function = {
            OPTION_ON_OFF       : self.send_knx_write_short_to_slave,
            OPTION_VAR          : self.send_knx_write_long_to_slave,
            OPTION_UP_DOWN      : self.send_knx_write_short_to_slave,
            OPTION_OPEN_CLOSE   : self.send_knx_write_short_to_slave,
            OPTION_STOP_UP_DOWN : self.send_knx_write_short_to_slave,
            OPTION_TEMPERATURE_W: self.send_knx_write_temp
        };
        self.sql = MasterSql();
        self._parser = DaemonConfigParser('/etc/greenleaf/master.conf');
        self.aes_slave_keys = slave_keys;

    def update_room_device_option(self, daemon_id, json_obj):
        """
        Update room_device_option table in database to set new values of the device described by 'json_obj'
        """
        if int(json_obj['type']) == KNX_RESPONSE:
            self.sql.update_room_device_option_resp(json_obj, daemon_id);
        elif int(json_obj['type']) == KNX_WRITE_SHORT:
            self.sql.update_room_device_option_write_short(json_obj, daemon_id);
        elif int(json_obj['type']) == KNX_WRITE_LONG:
            self.sql.update_room_device_option_write_long(json_obj, daemon_id);

    def protocol_knx(self, json_obj, dev, hostname):
        """
        KNX protocol data treatment function
        """
        new_obj = {
            "data": {
                "addr": str(dev['addr_dst']),
                "value": str(json_obj['data']['value'])
            }
        };
        self.knx_function[int(json_obj['data']['option_id'])](hostname, new_obj);

    def send_json_obj_to_slave(self, json_str, sock, hostname, aes_key, close_flag = True):
        """
        Send 'json_obj' to 'hostname' via 'sock'
        """
        hostname_key = '';
        if '.' in hostname:
            hostname_key = hostname.split('.')[0];
        else:
            hostname_key = hostname;
        AES.key_size = 32;
        aes_IV = AESManager.get_IV();
        encode_obj = AES.new(aes_key, AES.MODE_CBC, aes_IV);
        data2 = encode_obj.encrypt(json_str + (176 - len(json_str)) * ' ');
        sock.send(bytes(aes_IV, 'utf-8') + data2);
        if close_flag == True:
            sock.close();

    def send_knx_write_temp(self, hostname, json_obj):
        """
        Converts absolute value of temperature (Celsius) in 2 hexadecimal values for
        sending to KNX device
        """
        port = self._parser.getValueFromSection('connect', 'port');
        if not port:
            sys.exit(4);
        sock = socket.create_connection((hostname, port));
        val_str = json_obj['data']['value'];
        if '.' in val_str:
            val_str = val_str.split('.')[0];
        value = utils.convert_temperature_reverse(int(val_str));
        json_str = json.JSONEncoder().encode(
            {
                "packet_type": "knx_write_temp",
                "addr_to_send": json_obj['data']['addr'],
                "value": value[0] + ' ' + value[1]
            }
        );
        self.send_json_obj_to_slave(json_str, sock, hostname, self.aes_slave_keys[hostname]);

    def send_knx_write_long_to_slave(self, hostname, json_obj):
        """
        Constructs long write request and sends it to 'hostname'
        """
        port = self._parser.getValueFromSection('connect', 'port');
        if not port:
            sys.exit(4);
        sock = socket.create_connection((hostname, port));
        json_str = json.JSONEncoder().encode(
            {
                "packet_type": "knx_write_long",
                "addr_to_send": json_obj['data']['addr'],
                "value": hex(int(json_obj['data']['value']))
            }
        );
        self.send_json_obj_to_slave(json_str, sock, hostname, self.aes_slave_keys[hostname]);

    def send_knx_write_short_to_slave(self, hostname, json_obj):
        """
        Constructs short write request and sends it to 'hostname'
        """
        port = self._parser.getValueFromSection('connect', 'port');
        if not port:
            sys.exit(4);
        sock = socket.create_connection((hostname, port));
        json_str = json.JSONEncoder().encode(
            {
                "packet_type": "knx_write_short",
                "addr_to_send": json_obj['data']['addr'],
                "value": json_obj['data']['value']
            }
        );
        self.send_json_obj_to_slave(json_str, sock, hostname, self.aes_slave_keys[hostname]);

    def send_knx_read_request_to_slave(self, hostname, json_obj):
        """
        Constructs short read request and sends it to 'hostname'
        """
        port = self._parser.getValueFromSection('connect', 'port');
        if not port:
            sys.exit(4);
        sock = socket.create_connection((hostname, port));
        json_str = json.JSONEncoder().encode(
            {
                "packet_type": "knx_read_request",
                "addr_to_read": json_obj['data']['addr']
            }
        );
        self.send_json_obj_to_slave(json_str, sock, hostname, self.aes_slave_keys[hostname]);
Example #12
0
class MasterSql:
    """
    Master daemon SQL management class.
    """
    def __init__(self, log_flag = True):
        self.logger = Logger(log_flag, '/var/log/domomaster.log');
        self._parser = DaemonConfigParser(MASTER_CONF_FILE);
        self.db_username = self._parser.getValueFromSection(MASTER_CONF_MYSQL_SECTION,
                                                            MASTER_CONF_MYSQL_USER_ENTRY);
        self.db_passwd = self._parser.getValueFromSection(MASTER_CONF_MYSQL_SECTION,
                                                          MASTER_CONF_MYSQL_PASSWORD_ENTRY);
        self.db_dbname = self._parser.getValueFromSection(MASTER_CONF_MYSQL_SECTION,
                                                          MASTER_CONF_MYSQL_DB_NAME_ENTRY);
        if not self.db_username or not self.db_passwd or not self.db_dbname:
            frameinfo = getframeinfo(currentframe());
            self.logger.info("[ MASTER DAEMON " + frameinfo.filaname + ":" + str(frameinfo.lineno) + " ]: initialization error: wrong or missing SQL configuration.");
            sys.exit(1);

    def insert_hostlist_in_db(self, hostlist):
        """
        Update of the table containing the hosts. Inserts each host in 'hostlist'.
        """
        db = MysqlHandler(self.db_username, self.db_passwd, self.db_dbname);
        for host in hostlist:
            db.update_datas_in_table('ip_monitor',
                                     {"mac_addr": host._MacAddr},
                                     {"last_update": str(time.time()).split('.')[0],
                                      "ip_addr": host._IpAddr,
                                      "hostname": host._Hostname.split('.')[0]});
        db.personnal_query("DELETE FROM ip_monitor WHERE last_update<"+str(time.time()-7200).split('.')[0]);
        
        db.updatedb();

        query =  "UPDATE room_device ";
        query += "JOIN ip_monitor ON plus4=mac_addr ";
        query += "SET addr=ip_addr ";
        query += "WHERE protocol_id=6 AND plus4 != '' AND ip_addr != addr";
        self.mysql_handler_personnal_query(query);
        
        db.updatedb();
        
        db.close();

    def update_enocean_log(self, json_obj):
        """
        Update of the enocean log table with values from 'json_obj'
        """
        daemon_id = 0;
        db = MysqlHandler(self.db_username, self.db_passwd, self.db_dbname);
        daemons = db.get_datas_from_table_with_names('daemon', ['daemon_id', 'name', 'serial', 'secretkey']);
        for d in daemons:
            if json_obj['sender_name'] == d[2]:
                daemon_id = d[0];
                break;
        db.insert_datas_in_table('enocean_log',
                                 ['type', 'addr_src', 'addr_dest', 'eo_value', 't_date', 'daemon_id'],
                                 (json_obj['type'], json_obj['src_addr'], json_obj['dst_addr'],
                                  json_obj['value'], json_obj['date'], daemon_id));
        db.updatedb();
        db.close();
        return daemon_id;

    def update_knx_log(self, json_obj):
        """
        Update of the knx log table with values from 'json_obj'
        """
        daemon_id = 0;
        db = MysqlHandler(self.db_username, self.db_passwd, self.db_dbname);
        daemons = db.get_datas_from_table_with_names('daemon', ['daemon_id', 'name', 'serial', 'secretkey']);
        for d in daemons:
            if json_obj['sender_name'] == d[2]:
                daemon_id = d[0];
                break;

        db.insert_datas_in_table('knx_log', ["type", "addr_src", "addr_dest", "knx_value", "t_date", "daemon_id"],
                                 (json_obj['type'], json_obj['src_addr'], json_obj['dst_addr'], json_obj['value'],
                                  json_obj['date'],
                                  daemon_id));
        db.updatedb();
        db.close();
        return daemon_id;

    def update_room_device_option_write_long(self, json_obj, daemon_id):
        """
        Update of the table room_device_option with long KNX value
        """
        query = "SELECT option_id, room_device.room_device_id, addr_plus FROM room_device_option ";
        query += "JOIN room_device ON room_device_option.room_device_id=room_device.room_device_id ";
        query += "WHERE daemon_id=" + str(daemon_id) + " AND room_device_option.addr=\"";
        query += str(json_obj['dst_addr']) + "\"";
        res = self.mysql_handler_personnal_query(query);
        
        if len(res) == 0:
            query = "SELECT option_id, room_device.room_device_id FROM ";
            query += "room_device_option JOIN room_device ON ";
            query += "room_device_option.room_device_id=room_device.room_device_id WHERE ";
            query += "daemon_id=" + str(daemon_id) + " AND room_device_option.addr_plus=\"";
            query += str(json_obj['dst_addr']) + "\"";
            res = self.mysql_handler_personnal_query(query);
        
        for r in res:
            if int(r[0]) == MasterDaemon.OPTION_VAR:
                up = 'UPDATE room_device_option SET valeur=';
                if json_obj['value'] == 0:
                    up += '0';
                else:
                    up += '1';
                up += " WHERE room_device_id=" + str(r[1]);
                up += " AND option_id=12";
                self.logger.info('update_room_device_option write_long: up = ' + up);
                self.mysql_handler_personnal_query(up);
                
                query = "UPDATE room_device_option SET ";
                query += "valeur=\"" + str(json_obj['value']) + "\" ";
                query += "WHERE room_device_id=" + str(r[1]) + " AND option_id="+str(r[0]);
                self.mysql_handler_personnal_query(query);
            elif int(r[0]) == MasterDaemon.OPTION_TEMPERATURE or int(r[0]) == MasterDaemon.OPTION_TEMPERATURE_W:
                val = int(json_obj['value']);
                res = utils.convert_temperature(val);
                query = "UPDATE room_device_option JOIN room_device ON ";
                query += "room_device_option.room_device_id=room_device.room_device_id SET ";
                query += "valeur=\"" + str(res) + "\" WHERE daemon_id=" + str(daemon_id);
                query += " AND room_device_option.addr=\"" + str(json_obj['dst_addr']) + "\"";
                self.logger.info('update_room_device_option write_long: query = ' + query);
                self.mysql_handler_personnal_query(query);
            else:
                up = "UPDATE room_device_option SET valeur=\"" + str(json_obj['value'])
                up += "\" WHERE room_device_id=" + str(r[1]) + " AND option_id=\"" + str(r[0]) + "\"";
                self.logger.info('update_room_device_option write_long: up = ' + up)
                self.mysql_handler_personnal_query(up);

    def update_room_device_option_resp(self, json_obj, daemon_id):
        """
        Update of the table room_device_option with resp KNX value
        """
        query = "SELECT option_id, room_device.room_device_id FROM ";
        query += "room_device_option JOIN room_device ON ";
        query += "room_device_option.room_device_id=room_device.room_device_id WHERE ";
        query += "daemon_id=" + str(daemon_id) + " AND room_device_option.addr=\"";
        query += str(json_obj['dst_addr']) + "\"";
        
        res = self.mysql_handler_personnal_query(query);
        if type(res).__name__ == 'list':
            for r in res:
                query = "UPDATE room_device_option JOIN room_device ON ";
                query += "room_device_option.room_device_id=room_device.room_device_id SET ";
                if int(r[0]) == OPTION_TEMPERATURE:
                    val = int(json_obj['value']);
                    val = utils.convert_temperature(val);
                    query += "valeur=\"" + str(val) + "\" WHERE daemon_id=" + str(daemon_id);
                    query += " AND room_device_option.addr=\"" + str(json_obj['dst_addr']) + "\"";
                    self.logger.info("update_room_device_option resp query = " + query);
                    self.mysql_handler_personnal_query(query);
                else:
                    query += "valeur=\"" + str(json_obj['value']) + "\" WHERE daemon_id=" + str(daemon_id);
                    query += " AND addr_plus=\"" + str(json_obj['dst_addr']) + "\"";
                    self.logger.info("update_room_device_option resp query = " + query);
                    self.mysql_handler_personnal_query(query);
        else:
            query = "UPDATE room_device_option JOIN room_device ON ";
            query += "room_device_option.room_device_id=room_device.room_device_id SET ";
            if int(r[0]) == OPTION_TEMPERATURE:
                val = int(json_obj['value']);
                val = utils.convert_temperature(val);
                query += "valeur=\"" + str(val) + "\" WHERE daemon_id=" + str(daemon_id);
                query += " AND room_device_option.addr=\"" + str(json_obj['dst_addr']) + "\"";
                self.logger.info("update_room_device_option resp query = " + query);
                self.mysql_handler_personnal_query(query);
            else:
                query += "valeur=\"" + str(json_obj['value']) + "\" WHERE daemon_id=" + str(daemon_id);
                query += " AND addr_plus=\"" + str(json_obj['dst_addr']) + "\"";
                self.logger.info('update_room_device_option resp query = ' + query);
                self.mysql_handler_personnal_query(query);

    def update_room_device_option_write_short(self, json_obj, daemon_id):
        """
        Update of the table room_device_option with short KNX value
        """
        query = "SELECT option_id, room_device.room_device_id FROM ";
        query += "room_device_option JOIN room_device ON ";
        query += "room_device_option.room_device_id=room_device.room_device_id WHERE ";
        query += "daemon_id=" + str(daemon_id) + " AND room_device_option.addr=\"";
        query += str(json_obj['dst_addr']) + "\"";
        #self.logger.info("update_room_device_option write_short query : " + query);
        res = self.mysql_handler_personnal_query(query);
        
        if len(res) == 0:
            query = "SELECT option_id, room_device.room_device_id FROM ";
            query += "room_device_option JOIN room_device ON ";
            query += "room_device_option.room_device_id=room_device.room_device_id WHERE ";
            query += "daemon_id=" + str(daemon_id) + " AND room_device_option.addr_plus=\"";
            query += str(json_obj['dst_addr']) + "\"";
            #self.logger.info("update_room_device_option write_short query : " + query);
            res = self.mysql_handler_personnal_query(query);
        
        for r in res:
            if (int(r[0]) == MasterDaemon.OPTION_ON_OFF or int(r[0]) == MasterDaemon.OPTION_UP_DOWN or int(r[0]) == MasterDaemon.OPTION_OPEN_CLOSE):
                up = 'UPDATE room_device_option SET valeur=';
                if json_obj['value'] == 0:
                    up += '0';
                else:
                    up += '255';
                up += ' WHERE room_device_id=' + str(r[1]) + " AND option_id=13";
                self.logger.info("update_room_device_option write_short up1: " + up)
                self.mysql_handler_personnal_query(up);
            up = "UPDATE room_device_option SET valeur=" + str(json_obj['value']);
            up += " WHERE room_device_id=" + str(r[1]) + " AND option_id=" + str(r[0]) + "";
            self.logger.info("update_room_device_option write_short up2: " + up)
            self.mysql_handler_personnal_query(up);

    def mysql_handler_personnal_query(self, query):
        """
        Sends personnal query to the database and returns the result
        """
        db = MysqlHandler(self.db_username, self.db_passwd, self.db_dbname);
        res = db.personnal_query(query);
        db.updatedb();
        db.close();
        return res;

    def get_daemons(self):
        """
        Retrieves each daemon stored in the database
        """
        db = MysqlHandler(self.db_username, self.db_passwd, self.db_dbname);
        daemons = db.get_datas_from_table_with_names('daemon', ['daemon_id', 'name', 'serial', 'secretkey']);
        return daemons;
Example #13
0
class SlaveDaemon:
    """
    Main slave class
    It does communication between different monitors (KNX, EnOcean... in C) and the masters (servers)
    """
    def __init__(self, log_flag):
        self.logger = Logger(log_flag, LOG_FILE);
        self.logger.info('Started Domoleaf Slave daemon');
        print('######## SLAVE DAEMON #######')
        self.connected_masters = {};
        self.connected_knx = [];
        self.connected_enocean = [];
        self.connected_cron = [];
        self.clients = [];
        self._scanner = Scanner();
        self._hostlist = [];
        myhostname = socket.gethostname().upper()
        if SLAVE_NAME_PREFIX in myhostname:
            self._scanner.scan();
            self._hostlist = self._scanner._HostList;
        else:
            self._hostlist.append(Host('', '127.0.0.1', myhostname));
        self._parser = DaemonConfigParser(SLAVE_CONF_FILE);
        self.encrypt_keys = {};
        self.knx_sock = None;
        self.master_sock = None;
        self.enocean_sock = None;
        self.cron_sock = None;
        self.private_aes = self._parser.getValueFromSection('personnal_key', 'aes');
        self.wifi_init(self._parser.getValueFromSection('wifi', 'ssid'), self._parser.getValueFromSection('wifi', 'password'), self._parser.getValueFromSection('wifi', 'encryption'), self._parser.getValueFromSection('wifi', 'mode'), 0);
        self.connect_port = self._parser.getValueFromSection(SLAVE_CONF_CONNECT_SECTION, SLAVE_CONF_CONNECT_PORT_ENTRY);
        self.functions = {
            KNX_READ_REQUEST    : self.knx_read_request,
            KNX_WRITE_SHORT     : self.knx_write_short,
            KNX_WRITE_LONG      : self.knx_write_long,
            KNX_WRITE_TEMP      : self.knx_write_temp,
            CHECK_SLAVE         : self.check_slave,
            MONITOR_IP          : self.monitor_ip,
            DATA_UPDATE         : self.update,
            SEND_TECH           : self.send_tech,
            SEND_ALIVE          : self.send_alive,
            SEND_INTERFACES     : self.send_interfaces,
            SHUTDOWN_D3         : self.shutdown_d3,
            REBOOT_D3           : self.reboot_d3,
            WIFI_UPDATE         : self.wifi_update
        };

    def update(self, json_obj, connection):
        p = call(['dpkg', '--configure', '-a'])
        call(['apt-get', 'update']);
        call(['DEBIAN_FRONTEND=noninteractive', 'apt-get', 'install', 'domoslave', '-y']);
        version = os.popen("dpkg-query -W -f='${Version}\n' domoslave").read().split('\n')[0];
        json_str = '{"packet_type": "update_finished", "aes_pass": "******", "new_version": ' + version + '}';
        encrypt_IV = AESManager.get_IV();
        spaces = 16 - len(json_str) % 16;
        json_str = json_str + (spaces * ' ');
        encode_obj = AES.new(self.private_aes, AES.MODE_CBC, encrypt_IV);
        data = encode_obj.encrypt(json_str);
        # faut ouvrir une nouvelle socket pour envoyer la nouvelle version
        # connection.send(bytes(encrypt_IV, 'utf-8') + data);

    def run(self):
        """
        Initialization of the sockets for listenning incomming connections.
        Calls the loop function.
        """
        self.run = True;
        self.knx_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM);
        self.master_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM);
        self.enocean_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM);
        self.cron_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM);
        self.knx_sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1);
        self.master_sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1);
        self.enocean_sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1);
        self.cron_sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1);
        port = self._parser.getValueFromSection(SLAVE_CONF_KNX_SECTION, SLAVE_CONF_KNX_PORT_ENTRY);
        if not port:
            sys.exit(2);
        port_master = self._parser.getValueFromSection(SLAVE_CONF_LISTEN_SECTION, SLAVE_CONF_LISTEN_PORT_ENTRY);
        if not port_master:
            sys.exit(2);
        port_enocean = self._parser.getValueFromSection(SLAVE_CONF_ENOCEAN_SECTION, SLAVE_CONF_ENOCEAN_PORT_ENTRY);
        if not port_enocean:
            sys.exit(2);
        port_cron = self._parser.getValueFromSection(SLAVE_CONF_CRON_SECTION, SLAVE_CONF_CRON_PORT_ENTRY);
        if not port_cron:
            sys.exit(2);
        self.knx_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1);
        self.master_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1);
        self.enocean_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1);
        self.cron_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1);
        self.knx_sock.bind(('', int(port)));
        self.master_sock.bind(('', int(port_master)));
        self.enocean_sock.bind(('', int(port_enocean)));
        self.cron_sock.bind(('127.0.0.1', int(port_cron)));
        self.knx_sock.listen(MAX_KNX);
        self.master_sock.listen(MAX_MASTERS);
        self.enocean_sock.listen(MAX_ENOCEAN);
        self.cron_sock.listen(MAX_CRON);
        self.send_monitor_ip()
        self.loop();

    def accept_knx(self):
        """
        Get available sockets for reading on the KNX socket.
        """
        rlist, wlist, elist = select.select([self.knx_sock], [], [], SELECT_TIMEOUT);
        append = self.connected_knx.append       
        for connection in rlist:
            new_knx, addr = connection.accept();
            append(new_knx);
        self.receive_from_knx(self.connected_knx);

    def accept_masters(self):
        """
        Get available sockets for reading on the master socket.
        """
        rlist, wlist, elist = select.select([self.master_sock], [], [], SELECT_TIMEOUT);
        masters_socks = [];
        append = masters_socks.append;
        for item in rlist:
            new_conn, addr = item.accept();
            append(new_conn);
        self.receive_from_masters(masters_socks);

    def accept_enocean(self):
        """
        Get available sockets for reading on the EnOcean socket.
        """
        rlist, wlist, elist = select.select([self.enocean_sock], [], [], SELECT_TIMEOUT);
        enocean_socks = [];
        append = enocean_socks.append;
        append_connected = self.connected_enocean.append;
        for item in rlist:
            new_conn, addr = item.accept();
            append(new_conn);
            append_connected(new_conn);
        self.receive_from_enocean(enocean_socks);

    def accept_cron(self):
        """
        Get available sockets for reading on the Cron socket.
        """
        rlist, wlist, elist = select.select([self.cron_sock], [], [], SELECT_TIMEOUT);
        cron_socks = [];
        append = cron_socks.append;
        append_connected = self.connected_cron.append;
        for item in rlist:
            new_conn, addr = item.accept();
            append(new_conn);
            append_connected(new_conn);
        self.receive_from_cron(cron_socks);

    def parse_data(self, data, connection):
        """
        Calls the wanted function with the packet_type described in 'data' (JSON syntax)
        """
        json_obj = json.JSONDecoder().decode(data);
        if json_obj['packet_type'] in self.functions.keys():
            self.functions[json_obj['packet_type']](json_obj, connection);
        else:
            raise Exception(str(json_obj['packet_type'])+": is not a valid packet type");

    def knx_read_request(self, json_obj, connection):
        """
        System call of "groupread" with parameters.
        """
        call(['knxtool', CALL_GROUPREAD, EIB_URL, json_obj['addr_to_read']]);

    def knx_write_temp(self, json_obj, connection):
        """
        System call of "groupwrite" with parameters.
        Almost the same as "knx_write_long" function, except that parameters are not the same
        """
        val = json_obj['value'].split(' ')
        call(['knxtool', CALL_GROUPWRITE, EIB_URL, json_obj['addr_to_send'], val[0], val[1]]);

    def knx_write_short(self, json_obj, connection):
        """
        System call of "groupswrite" with parameters.
        """
        call(['knxtool', CALL_GROUPSWRITE, EIB_URL, json_obj['addr_to_send'], str(json_obj['value'])]);

    def knx_write_long(self, json_obj, connection):
        """
        System call of "groupwrite" with parameters.
        """
        call(['knxtool', CALL_GROUPWRITE, EIB_URL, json_obj['addr_to_send'], str(json_obj['value'])]);

    def receive_from_masters(self, masters_to_read):
        """
        Read data comming from masters and call "parse_data" function.
        """
        for master in masters_to_read:
            data = master.recv(4096);
            decrypt_IV = data[:16].decode();
            decode_obj = AES.new(self.private_aes, AES.MODE_CBC, decrypt_IV);
            data2 = decode_obj.decrypt(data[16:]);
            self.parse_data(data2.decode(), master);
            master.close();

    def receive_from_knx(self, knx_to_read):
        """
        Read data from monitor KNX and transmits to master.
        """
        for knx in knx_to_read:
            data = knx.recv(TELEGRAM_LENGTH);
            if data:
                self.send_knx_data_to_masters(data);
            if knx in self.connected_knx:
                knx.close();
                self.connected_knx.remove(knx);

    def receive_from_enocean(self, enocean_to_read):
        """
        Read data from monitor EnOcean and transmits to master.
        """
        for enocean in enocean_to_read:
            data = enocean.recv(4096);
            if data:
                self.send_enocean_data_to_masters(data);
            if enocean in self.connected_enocean:
                enocean.close();
                self.connected_enocean.remove(enocean);

    def receive_from_cron(self, cron_to_read):
        """
        Receive data from Cron and execute it.
        """
        for cron in cron_to_read:
            data = cron.recv(4096);
            if data:
                json_str = json.JSONEncoder().encode(
                    {
                        "packet_type": data.decode()
                    }
                );
                self.parse_data(json_str, cron);
            if cron in self.connected_cron:
                cron.close();
                self.connected_cron.remove(cron);

    def check_slave(self, json_obj, connection):
        """
        Callback called each time a check_slave packet is received.
        Used to confirm the existence of this daemon.
        """
        interface_knx = self._parser.getValueFromSection(SLAVE_CONF_KNX_SECTION, SLAVE_CONF_KNX_INTERFACE);
        interface_enocean = self._parser.getValueFromSection(SLAVE_CONF_ENOCEAN_SECTION, SLAVE_CONF_ENOCEAN_INTERFACE);
        version = os.popen("dpkg-query -W -f='${Version}\n' domoslave").read().split('\n')[0];
        json_str = '{"packet_type": "check_slave", "aes_pass": "******", "version": "' + version + '", "interface_knx": "' + interface_knx + '", "interface_enocean": "' + interface_enocean + '"}';
        master_hostname = str(json_obj['sender_name']);
        encrypt_IV = AESManager.get_IV();
        spaces = 16 - len(json_str) % 16;
        json_str = json_str + (spaces * ' ');
        encode_obj = AES.new(self.private_aes, AES.MODE_CBC, encrypt_IV);
        data = encode_obj.encrypt(json_str);
        connection.send(bytes(encrypt_IV, 'utf-8') + data);

    def monitor_ip(self, json_obj, connection):
        """
        Re scan the local network to refresh hostlist.
        """
        self._scanner.scan();
        self._hostlist = self._scanner._HostList;

    def send_monitor_ip(self):
        json_str = json.JSONEncoder().encode(
            {
                "packet_type": "monitor_ip"
            }
        );
        self.send_data_to_all_masters(json_str);

    def loop(self):
        """
        Main daemon loop.
        """
        while self.run:
            try:
                self.accept_knx();
            except Exception as e:
                frameinfo = getframeinfo(currentframe());
                self.logger.error('in loop accept_knx: '+str(e));
                print('in loop accept_knx: ',str(e));
            except KeyboardInterrupt as e:
                frameinfo = getframeinfo(currentframe());
                self.logger.error('in loop: Keyboard interrupt');
            try:
                self.accept_masters();
            except Exception as e:
                frameinfo = getframeinfo(currentframe());
                self.logger.error('in loop accept_masters: '+str(e));
                print('in loop accept_masters: ',str(e));
            except KeyboardInterrupt as e:
                frameinfo = getframeinfo(currentframe());
                self.logger.error('in loop: Keyboard interrupt');
            try:
                self.accept_enocean();
            except Exception as e:
                frameinfo = getframeinfo(currentframe());
                self.logger.error('in loop accept_enocean: '+str(e));
                print('in loop accept_enocean: ',str(e));
            except KeyboardInterrupt as e:
                frameinfo = getframeinfo(currentframe());
                self.logger.error('in loop: Keyboard interrupt');
            try:
                self.accept_cron();
            except Exception as e:
                frameinfo = getframeinfo(currentframe());
                self.logger.error('in loop accept_cron: '+str(e));
                print('in loop accept_cron: ',str(e));
            except KeyboardInterrupt as e:
                frameinfo = getframeinfo(currentframe());
                self.logger.error('in loop: Keyboard interrupt');
        
    def stop(self):
        """
        Stop the daemon and closes all sockets.
        """
        for name, sock in self.connected_masters.items():
            sock.close();
        for knx in self.connected_knx:
            knx.close();
        self.knx_sock.close();

    def connect_to_masters(self):
        """
        Stored every device on network which have his hostname beginning by "MD3" and stores it
        in the self.connected_masters dict(), with hostnames as keys and sockets freshly open as values.
        """
        hostname = socket.gethostname()
        self.connected_masters = {};
        for host in self._hostlist:
            if MASTER_NAME_PREFIX in host._Hostname or str(host._IpAddr) == '127.0.0.1':
                if not self.connect_port:
                    self.logger.error('in connect_to_masters: No '+SLAVE_CONF_CONNECT_PORT_ENTRY+' in '+SLAVE_CONF_CONNECT_SECTION+' section or maybe no such '+SLAVE_CONF_CONNECT_SECTION+' defined');
                    sys.exit(1);
                try:
                    self.logger.debug('Connecting to '+str(host._IpAddr)+':'+str(self.connect_port));
                    sock = socket.create_connection((host._IpAddr, self.connect_port));
                    hostname = host._Hostname.split('.')[0];
                    self.connected_masters[host._Hostname] = sock;
                except Exception as e:
                    frameinfo = getframeinfo(currentframe());
                    self.logger.error('in connect_to_masters: '+str(e));
                    pass;

    def send_knx_data_to_masters(self, data):
        """
        Converts 'data' from bytes to a clear KNX datagran, and sends it to available slaves.
        """
        ctrl = int(data[0]);
        src_addr = int.from_bytes(data[1:3], byteorder='big');
        dst_addr = int.from_bytes(data[3:5], byteorder='big');
        data_len = int.from_bytes(data[5:6], byteorder='big');
        telegram_data = data[6:7 + data_len];
        typ = -1;
        value = 0;
        if telegram_data[1] & 0xC0 == 0x00:             # read
            typ = 0;
        elif telegram_data[1] & 0xC0 == 0x40:           # resp
            typ = 1;
            if data_len == 2:
                value = int(telegram_data[1] & 0x0f);
            elif data_len > 2:
                value = int.from_bytes(telegram_data[2:data_len], byteorder='big');
        elif telegram_data[1] & 0xC0 == 0x80:           # write
            typ = 2;
            if data_len == 2:
                value = int(telegram_data[1] & 0x0f);
            elif data_len > 2:
                typ = 3;
                value = int.from_bytes(telegram_data[2:data_len], byteorder='big');
        json_str = json.JSONEncoder().encode(
            {
                "packet_type": "monitor_knx",
                "type": typ,
                "src_addr": individual2string(src_addr),
                "dst_addr": group2string(dst_addr),
                "date": str(time.time()).split('.')[0],
                "value": value,
                "sender_name": socket.gethostname()
            }
        );
        self.send_data_to_all_masters(json_str);

    def send_enocean_data_to_masters(self, data):
        """
        Converts 'data' from bytes to a clear EnOcean datagran, and sends it to available slaves.
        """
        if (data[4] == PACKET_TYPE_RADIO_ERP1): # si le packet_type == radio_erp1
            data_len = int.from_bytes(data[1:2], byteorder='big');
            opt_data_len = int(data[3]);
            src_str = "%X" % int.from_bytes(data[1+data_len:5+data_len], byteorder='big');
            if len(src_str) < 8:
                src_str = "0"+src_str;
            json_dict = {
                "packet_type": "monitor_enocean",
                "src_addr": src_str,
                "dst_addr": "%X" % int.from_bytes(data[261:265 + opt_data_len], byteorder='big'),
                "date": str(time.time()).split('.')[0],
                "sender_name": socket.gethostname(),
                "type": int(data[6])
            };
            if data[6] == RORG_NORMAL:
                json_dict['value'] = int(data[7]);
            elif data[6] == RORG_TEMPERATURE:
                json_dict['value'] = float(40 - ((40 / 255) * int(data[9])));
            json_str = json.JSONEncoder().encode(json_dict);
            self.send_data_to_all_masters(json_str);

    def send_data_to_all_masters(self, json_str):
        """
        Sends a string 'json_str' to available slaves on network.
        """
        self.connect_to_masters();
        # ici envoyer a tous les masters
        for name in self.connected_masters.keys():
            try:
                master = self.connected_masters[name];
                AES.key_size = 32;
                aes_IV = AESManager.get_IV();
                encode_obj = AES.new(self.private_aes, AES.MODE_CBC, aes_IV);
                spaces = 16 - len(json_str) % 16;
                data2 = encode_obj.encrypt(json_str + (spaces * ' '));
                master.send(bytes(aes_IV, 'utf-8') + data2);
                master.close();
            except KeyError as e:
                self.logger.error('in send_data_to_all_masters: '+str(e));
                print(e);
                pass;

    def send_tech(self, json_obj, connection):
        json_str = json.JSONEncoder().encode(
            {
                "packet_type": "send_tech",
                "info": GLManager.TechInfo()
            }
        );
        self.send_data_to_all_masters(json_str);

    def send_alive(self, json_obj, connection):
        json_str = json.JSONEncoder().encode(
            {
                "packet_type": "send_alive",
                "info": GLManager.TechAlive()
            }
        );
        self.send_data_to_all_masters(json_str);

    def send_interfaces(self, json_obj, connection):
        try:
            if os.path.exists('/tmp/knxd'):
                call(['service', 'knxd', 'stop']);
            previous_val_knx = self._parser.getValueFromSection('knx', 'interface');
            previous_val_EnOcean = self._parser.getValueFromSection('enocean', 'interface');
            new_val = str(json_obj['interface_arg_knx'])
            self._parser.writeValueFromSection('knx', 'interface', new_val);
            self._parser.writeValueFromSection('knx', 'activated', str(json_obj['daemon_knx']));
            self._parser.writeValueFromSection('enocean', 'interface', str(json_obj['interface_arg_EnOcean']));
            if not previous_val_knx or previous_val_knx is None:
                call(['update-rc.d', 'knxd', 'defaults']);
                call(['update-rc.d', 'knxd', 'enable']);
            if not new_val or new_val is None:
                Popen(['systemctl', '-q', 'disable', 'knxd']);
            else:
                knx_edit = 'KNXD_OPTS="-e 1.0.254 -D -T -S -b ';
                if json_obj['interface_knx'] == 'tpuarts':
                    knx_edit += json_obj['interface_knx']+':/dev/'+new_val+'"';
                else:
                    knx_edit += json_obj['interface_knx']+':'+new_val+'"';
                conf_knx = open('/etc/knxd.conf', 'w');
                conf_knx.write(knx_edit+'\n');
                conf_knx.close();
                call(['service', 'knxd', 'start']);
                if json_obj['daemon_knx'] == 1:
                    if os.path.exists('/var/run/monitor_knx.pid'):
                        os.remove('/var/run/monitor_knx.pid');
                    Popen(['monitor_knx', 'ip:localhost', '--daemon']);
        except Exception as e:
            self.logger.error(e);
        json_str = '{"packet_type": "send_interfaces", "aes_pass": "******"}';
        master_hostname = str(json_obj['sender_name']);
        encrypt_IV = AESManager.get_IV();
        spaces = 16 - len(json_str) % 16;
        json_str = json_str + (spaces * ' ');
        encode_obj = AES.new(self.private_aes, AES.MODE_CBC, encrypt_IV);
        data = encode_obj.encrypt(json_str);
        connection.send(bytes(encrypt_IV, 'utf-8') + data);
        if previous_val_EnOcean != str(json_obj['interface_arg_EnOcean']):
            call(['service', 'domoslave', 'restart']);

    def shutdown_d3(self, json_obj, connection):
        call(['poweroff']);

    def reboot_d3(self, json_obj, connection):
        call(['reboot']);

    def wifi_init(self, ssid, password, security, mode, opt):
        try:
            ps_process = Popen(["ps", "-x"], stdout=PIPE);
            res = Popen(["grep", "hostapd"], stdin=ps_process.stdout, stdout=PIPE);
            res = res.stdout.read().decode().split("\n")[0].split(' ');
            ps_process.stdout.close();
            if res:
                while ('' in res):
                    res.remove('');
                call(['kill', '-9', res[0]]);
            ps_process = Popen(["ps", "-x"], stdout=PIPE);
            res = Popen(["grep", "wpa_supplicant"], stdin=ps_process.stdout, stdout=PIPE);
            res = res.stdout.read().decode().split("\n")[0].split(' ');
            ps_process.stdout.close();
            if res:
                while ('' in res):
                    res.remove('');
                call(['kill', '-9', res[0]]);
            call(['ifconfig', 'wlan0', 'down']);
            if mode == WIFI_MODE_DISABLED:
                if opt == 1:
                    call(['service', 'dnsmasq', 'stop']);
            elif mode == WIFI_MODE_CLIENT:
                call(['ifconfig', 'wlan0', 'up']);
                if opt == 1:
                    call(['service', 'dnsmasq', 'stop']);
                conf_file = open('/etc/network/interfaces', 'w');
                conf_str = ''.join(['auto lo\niface lo inet loopback\n\nallow-hotplug eth0\n',
                      'iface eth0 inet dhcp\n\nallow-hotplug usb0\niface usb0 inet dhcp\n\n',
                      'auto wlan0\niface wlan0 inet dhcp\n\twpa-conf ', WPA_SUPPLICANT_CONF_FILE, '\n']);
                conf_file.write(conf_str);
                conf_file.close();
                conf_file = open(WPA_SUPPLICANT_CONF_FILE, 'w');
                conf_str = ''.join(['ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev\n',
                      'update_config=1\nctrl_interface_group=0\neapol_version=1\n',
                      'ap_scan=1\n fast_reauth=1\n\n\nnetwork={\n\tdisabled=0\n',
                      '\tssid="', ssid, '"\n\tscan_ssid=0\n\tpriority=1\n']);
                if security == WIFI_SECURITY_WPA:
                    conf_str += ('\tproto=WPA\n\tkey_mgmt=WPA-PSK\n\tauth_alg=OPEN\n'+
                          '\tpairwise=TKIP CCMP\n\tgroup=TKIP CCMP\n\tpsk="'+password+'"\n');
                elif security == WIFI_SECURITY_WPA2:
                    conf_str += ('\tproto=RSN\n\tkey_mgmt=WPA-PSK\n\tauth_alg=OPEN\n\tpairwise=CCMP TKIP\n'+
                                   '\tgroup=CCMP TKIP\n\tpsk="'+password+'"\n');
                elif security == WIFI_SECURITY_WEP:
                    conf_str += '\tkey_mgmt=NONE\n\tauth_alg=SHARED\n';
                    if len(password) == 5 or len(password) == 10:
                        conf_str += '\tgroup=WEP40\n';
                    elif len(password) == 13 or len(password) == 26:
                        conf_str += '\tgroup=WEP104\n';
                    else:
                        conf_str += '\tgroup=WEP40 WEP104\n';
                    conf_str += '\twep_key0="'+password+'"\n\twep_tx_keyidx=0\n';
                conf_str += '\tpriority=1\n}\n';
                conf_file.write(conf_str);
                conf_file.close();
                call(['wpa_supplicant', '-Dnl80211', '-iwlan0', '-c' + WPA_SUPPLICANT_CONF_FILE , '-B']);
                call(['dhclient', 'wlan0']);
            elif mode == WIFI_MODE_ACCESS_POINT:
                call(['ifconfig', 'wlan0', '172.16.0.1', 'netmask', '255.255.255.0', 'up']);
                conf_file = open(HOSTAPD_CONF_FILE, 'w');
                conf_str = ''.join(['interface=wlan0\n\ndriver=nl80211\n\nssid=', ssid, '\n\n',
                      'hw_mode=g\n\nieee80211n=1\n\nchannel=6\n\nbeacon_int=100\n\n',
                      'dtim_period=2\n\nmax_num_sta=255\n\nrts_threshold=2347\n\n',
                      'fragm_threshold=2346\n\nmacaddr_acl=0\n\n']);
                if security == WIFI_SECURITY_WPA:
                    conf_str += ('auth_algs=1\n\nwpa=1\n\nwpa_passphrase='+password+'\n\n'+
                                 'wpa_key_mgmt=WPA-PSK\n\nwpa_pairwise=TKIP\n');
                elif security == WIFI_SECURITY_WPA2:
                    conf_str += ('auth_algs=1\n\nwpa=2\n\nwpa_passphrase='+password+'\n\n'+
                                 'wpa_key_mgmt=WPA-PSK\n\nwpa_pairwise=CCMP\n\nrsn_pairwise=CCMP\n');
                else:
                    self.logger.error('Wifi security = Unknown');
                conf_file.write(conf_str);
                conf_file.close();
                if opt == 1:
                    conf_file = open(DNSMASQ_CONF_FILE, 'w');
                    conf_str = 'domain-needed\ninterface=wlan0\ndhcp-range=172.16.0.2,172.16.0.254,12h\n';
                    conf_file.write(conf_str);
                    conf_file.close();
                    call(['service', 'dnsmasq', 'restart']);
                call(['iptables', '-t', 'nat', '-A', 'POSTROUTING', '-j', 'MASQUERADE']);
                call(['hostapd', HOSTAPD_CONF_FILE, '-B']);
            else:
                call(['ifconfig', 'wlan0', 'up']);
                self.logger.error('Wifi mode = Unknown');
        except Exception as e:
            self.logger.error(e);

    def wifi_update(self, json_obj, connection):
        try:
            self._parser.writeValueFromSection('wifi', 'ssid', json_obj['ssid']);
            self._parser.writeValueFromSection('wifi', 'password', json_obj['password']);
            self._parser.writeValueFromSection('wifi', 'encryption', json_obj['security']);
            self._parser.writeValueFromSection('wifi', 'mode', json_obj['mode']);
            self.wifi_init(json_obj['ssid'], json_obj['password'], json_obj['security'], json_obj['mode'], 1);
        except Exception as e:
            self.logger.error(e);
        json_str = '{"packet_type": "wifi_update", "aes_pass": "******"}';
        master_hostname = str(json_obj['sender_name']);
        encrypt_IV = AESManager.get_IV();
        spaces = 16 - len(json_str) % 16;
        json_str = json_str + (spaces * ' ');
        encode_obj = AES.new(self.private_aes, AES.MODE_CBC, encrypt_IV);
        data = encode_obj.encrypt(json_str);
        connection.send(bytes(encrypt_IV, 'utf-8') + data);
Example #14
0
class MasterDaemon:
    """
    Main class of the master daemon
    It provides communication between master and slave boxes and a part of the database management
    """
    def __init__(self, log_flag):
        self.logger = Logger(log_flag, LOG_FILE);
        self.logger.info('Started Greenleaf Master Daemon');
        self.d3config = {};
        self.aes_slave_keys = {};
        self.aes_master_key = None;
        self.connected_clients = {};
        self.sql = MasterSql();
        self._parser = DaemonConfigParser(MASTER_CONF_FILE);
        self.get_aes_slave_keys();
        self.reload_camera(None, None);
        self.scanner = Scanner(HOSTS_CONF);
        self.scanner.scan(False);
        self.hostlist = self.scanner._HostList;
        self.sql.insert_hostlist_in_db(self.scanner._HostList);
        self.knx_manager = KNXManager(self.aes_slave_keys);
        self.reload_d3config(None, None);
        self.protocol_function = {
            PROTOCOL_KNX        : KNXManager.protocol_knx,
            PROTOCOL_ENOCEAN    : self.protocol_enocean,
            PROTOCOL_IP         : self.protocol_ip
        };
        self.upnp_function = {
            UPNP_PLAY           : self.upnp_set_play,
            UPNP_PAUSE          : self.upnp_set_pause,
            UPNP_NEXT           : self.upnp_set_next,
            UPNP_PREVIOUS       : self.upnp_set_prev,
            UPNP_STOP           : self.upnp_set_stop,
            UPNP_VOLUME_UP      : self.upnp_set_volume_up,
            UPNP_VOLUME_DOWN    : self.upnp_set_volume_down,
            UPNP_SET_VOLUME     : self.upnp_set_volume
        };
        self.enocean_function = {};
        self.data_function = {
            DATA_MONITOR_KNX            	  : self.monitor_knx,
            DATA_MONITOR_IP             	  : self.monitor_ip,
            DATA_MONITOR_ENOCEAN        	  : self.monitor_enocean,
            DATA_MONITOR_BLUETOOTH      	  : self.monitor_bluetooth,
            DATA_KNX_READ               	  : self.knx_read,
            DATA_KNX_WRITE_S            	  : self.knx_write_short,
            DATA_KNX_WRITE_L            	  : self.knx_write_long,
            DATA_SEND_TO_DEVICE         	  : self.send_to_device,
            DATA_CRON_UPNP             		  : self.cron_upnp,
            DATA_SEND_MAIL              	  : self.send_mail,
            DATA_CHECK_SLAVE            	  : self.check_slave,
            DATA_RELOAD_CAMERA          	  : self.reload_camera,
            DATA_RELOAD_D3CONFIG        	  : self.reload_d3config,
            DATA_BACKUP_DB_CREATE_LOCAL       : self.backup_db_create_local,
            DATA_BACKUP_DB_REMOVE_LOCAL       : self.backup_db_remove_local,
            DATA_BACKUP_DB_LIST_LOCAL         : self.backup_db_list_local,
            DATA_BACKUP_DB_RESTORE_LOCAL      : self.backup_db_restore_local,
            DATA_UPDATE                 	  : self.update
        };

    def get_aes_slave_keys(self):
        """
        Get the secretkeys of each slave daemon stored in database
        """
        query = "SELECT serial, secretkey FROM daemon";
        res = self.sql.mysql_handler_personnal_query(query);
        self_hostname = socket.gethostname();
        for r in res:
            if SLAVE_NAME_PREFIX in r[0] or 'MD3' in r[0]:
                self.aes_slave_keys[r[0]] = r[1];
            elif self_hostname == r[0]:
                self.aes_slave_keys[r[0]] = r[1];
                self.aes_master_key = r[1];
        print(self.aes_slave_keys)

    def stop(self):
        """
        Stops the daemon and closes sockets
        """
        flag = False;
        while not flag:
            flag = True;
            for client in self.connected_clients.values():
                flag = False;
                client.close();
                break;
        self.slave_connection.close();
        sys.exit(0);

    def run(self):
        """
        Initialization of the connections and accepting incomming communications
        """
        self.slave_connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM);
        self.cmd_connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM);
        self.slave_connection.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1);
        self.cmd_connection.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1);
        s_port = self._parser.getValueFromSection(MASTER_CONF_LISTEN_SECTION, MASTER_CONF_LISTEN_PORT_SLAVE_ENTRY);
        c_port = self._parser.getValueFromSection(MASTER_CONF_LISTEN_SECTION, MASTER_CONF_LISTEN_PORT_CMD_ENTRY);
        if not s_port:
            frameinfo = getframeinfo(currentframe());
            self.logger.error('in run: No slave listening port defined in ' + MASTER_CONF_FILE);
            sys.exit(1);
        if not c_port:
            frameinfo = getframeinfo(currentframe());
            self.logger.error('in run: No command listening port defined in ' + MASTER_CONF_FILE);
            sys.exit(1);
        self.slave_connection.bind(('', int(s_port)));
        self.slave_connection.listen(MAX_SLAVES);
        self.cmd_connection.bind(('', int(c_port)));
        self.cmd_connection.listen(MAX_CMDS);
        self.loop();

    def loop(self):
        """
        Main loop. Waits for new connections.
        """
        self.run = True;
        while self.run:
            try:
                rlist, wlist, elist = select.select([self.slave_connection], [], [], SELECT_TIMEOUT);
                for connection in rlist:
                    self.accept_new_slave_connection(connection);
                rlist, wlist, elist = select.select([self.cmd_connection], [], [], SELECT_TIMEOUT);
                for connection in rlist:
                    self.accept_new_cmd_connection(connection);
            except KeyboardInterrupt as e:
                frameinfo = getframeinfo(currentframe());
                self.logger.info('in loop: Keyboard interrupt: leaving program');
                print("[ MASTER DAEMON " + frameinfo.filename + ":" + str(frameinfo.lineno) + " ]: Keyboard Interrupt");
                self.stop();
                sys.exit(0);
            except ValueError as e:
                frameinfo = getframeinfo(currentframe());
                self.logger.error('in loop: Value error: ' + str(e));
                print("[ MASTER DAEMON " + frameinfo.filename + ":" + str(frameinfo.lineno) + "]: Value Error");
                print(e);
                pass;

    def accept_new_cmd_connection(self, connection):
        """
        Gets new mastercommand connections and threads the treatment.
        """
        new_connection, addr = connection.accept();
        r = CommandReceiver(new_connection, self);
        r.start();

    def accept_new_slave_connection(self, connection):
        """
        Gets new slave connections and threads the treatment.
        """
        new_connection, addr = connection.accept();
        for host in self.hostlist:
            if addr[0] == host._IpAddr:
                hostname = host._Hostname.split('.')[0];
                r = SlaveReceiver(new_connection, hostname, self);
                r.start();

    def parse_data(self, data, connection):
        """
        Once data are received whether from mastercommand or slave, the function of the packet_type in data is called.
        """
        json_obj = json.JSONDecoder().decode(data);
        if json_obj['packet_type'] in self.data_function.keys():
            self.data_function[json_obj['packet_type']](json_obj, connection);
        else:
            frameinfo = getframeinfo(currentframe());

    def update(self, json_obj, connection):
        call(['apt-get', 'update']);
        call(['apt-get', 'install', 'glmaster', 'glslave', '-y']);
        hostname = socket.gethostname();
        if '.' in hostname:
            hostname = hostname.split('.')[0];
        version_file = open('/etc/greenleaf/.glmaster.version', 'r');
        if not version_file:
            self.logger.error("File not found: /etc/greenleaf/.glmaster.version");
            print("File not found: /etc/greenleaf/.glmaster.version");
            return;
        version = version_file.read();
        if '\n' in version:
            version = version.split('\n')[0];
        query = 'UPDATE daemon SET version="' + version + '" WHERE name="' + hostname + '"';
        self.sql.mysql_handler_personnal_query(query);
        query = 'UPDATE configuration SET configuration_value="' + version + '" WHERE configuration_id=4';
        self.sql.mysql_handler_personnal_query(query);
        json_obj['data'].append(hostname);
        port = self._parser.getValueFromSection('connect', 'port');
        for host in self.hostlist:
            sock = socket.create_connection((host._IpAddr, port));
            if host._Hostname.startswith('MD3') or host._Hostname.startswith('SD3') and host._Hostname not in json_obj['data']:
                json_str = json.JSONEncoder().encode(json_obj);
                sock.send(bytes(json_str, 'utf-8'));
                data = sock.recv(4096);
                decrypt_IV = data[:16].decode();
                decode_obj = AES.new(self.aes_master_key, AES.MODE_CBC, decrypt_IV);
                data2 = decode_obj.decrypt(data[16:]).decode();
                version = data2['new_version'];
                query = 'UPDATE daemon SET version="' + version + '" WHERE name="' + host._Hostname + '"';
                self.sql.mysql_handler_personnal_query(query);

    def backup_db_create_local(self, json_obj, connection):
        filename = '/etc/greenleaf/sql/backup/mastercommand_backup_';
        t = str(time.time());
        if '.' in t:
            t = t.split('.')[0];
        filename += t;
        filename += '.sql';
        os.popen("mysqldump --defaults-file=/etc/mysql/debian.cnf mastercommand > " + filename);

    def backup_db_remove_local(self, json_obj, connection):
        filename = '/etc/greenleaf/sql/backup/';
        filename += json_obj;
        filename += '.sql';
        if json_obj[0] == '.' or json_obj[0] == '/':
            print('The filename is corrupted. Aborting database file removing.')
            return;
        try:
            os.stat(filename);
        except Exception as e:
            print("The database file to remove does not exists.")
            print(e)
            return;
        os.remove(filename);

    def backup_db_list_local(self, json_obj, connection):
        json_obj = [];
        backup_list = os.listdir('/etc/greenleaf/sql/backup/')
        for f in backup_list:
            s = os.stat('/etc/greenleaf/sql/backup/' + f);
            if '.sql' in f:
                f = f.split('.sql')[0];
				json_obj.append({"name": f, "size": s.st_size});
		json_sorted = sorted(json_obj, key=lambda json_obj: json_obj['name']);
		json_str = json.JSONEncoder().encode(json_sorted);
		connection.send(bytes(json_str, 'utf-8'));
Example #15
0
#!/usr/bin/python3

import sys
sys.path.append('/usr/lib/domoleaf')
import socket
import json
from DaemonConfigParser import *;

if __name__ == "__main__":
    try:
        parser = DaemonConfigParser('/etc/domoleaf/master.conf')
        ip = '127.0.0.1'
        port = parser.getValueFromSection('listen', 'port_cmd')
        s = socket.create_connection((ip, port))
        obj = {
            "packet_type": "check_all_schedules",
            "data" : ""
        }
        obj_str = json.JSONEncoder().encode(obj)
        s.send(obj_str.encode())
        s.close()
    except Exception as e:
        print(str(e))
Example #16
0
class KNXManager:

    ## The constructor.
    #
    # @param slave_keys Array containing the AES keys of all the slaves.
    # @return None
    def __init__(self, slave_keys):
        ## Logger object for formatting and printing logs
        self.logger = Logger(DEBUG_MODE, LOG_FILE);
        ## SQL object for managing database
        self.sql = MasterSql();
        self._parser = DaemonConfigParser('/etc/domoleaf/master.conf');
        ## Object containing AES keys for encrypted communications
        self.aes_slave_keys = slave_keys;

    ## Updates room_device_option table in database to set new values of the device described by json_obj.
    #
    # @param daemon_id The ID of a slave daemon.
    # @param json_obj JSON object containing the values to update.
    # @param db The database handler.
    #
    # @return The result of the query.
    def update_room_device_option(self, daemon_id, json_obj, db):
        if int(json_obj['type']) == KNX_RESPONSE:
            return self.sql.update_room_device_option_resp(json_obj, daemon_id, db);
        elif int(json_obj['type']) == KNX_WRITE_SHORT:
            return self.sql.update_room_device_option_write_short(json_obj, daemon_id, db);
        elif int(json_obj['type']) == KNX_WRITE_LONG:
            return self.sql.update_room_device_option_write_long(json_obj, daemon_id, db);

    ## Sends a JSON object to a slave daemon.
    #
    # @param json_str The data to send.
    # @param sock The socket used to send the data.
    # @param hostname The hostname of the slave daemon.
    # @param aes_key The AES key of the slave daemon to encrypt data.
    # @return None
    def send_json_obj_to_slave(self, json_str, sock, hostname, aes_key):
        hostname_key = '';
        if '.' in hostname:
            hostname_key = hostname.split('.')[0];
        else:
            hostname_key = hostname;
        AES.key_size = 32;
        aes_IV = AESManager.get_IV();
        encode_obj = AES.new(aes_key, AES.MODE_CBC, aes_IV);
        spaces = 16 - len(json_str) % 16;
        data2 = encode_obj.encrypt(json_str + (spaces * ' '));
        sock.send(bytes(aes_IV, 'utf-8') + data2);

    ## Changes the speed value of a fan.
    #
    # @param json_obj JSON object containing the values to write.
    # @param dev Object containing informations about the device.
    # @param hostname The hostname of the slave to who send the packet.
    # @return None
    def send_knx_write_speed_fan(self, json_obj, dev, hostname):
        port = self._parser.getValueFromSection('connect', 'port');
        sock = socket.create_connection((hostname, port));
        if not port:
            sys.exit(4);
        if json_obj['data']['value'] == '1':
            query = ''.join(['SELECT option_id, addr, dpt_id FROM room_device_option WHERE room_device_id=',
                           str(dev['room_device_id']),
                           ' AND option_id IN(400, 401, 402, 403, 404, 405, 406) AND status=1']);
            res = self.sql.mysql_handler_personnal_query(query);
            for line in res:
                if str(line[2]) == "51" and str(line[0]) == str(json_obj['data']['option_id']):
                    val = str(line[0]).split('40')[1];
                    json_str = json.JSONEncoder().encode(
                        {
                            "packet_type": "knx_write_long",
                            "addr_to_send": line[1],
                            "value": val
                        }
                    );
                    self.send_json_obj_to_slave(json_str, sock, hostname, self.aes_slave_keys[hostname]);
                    return;
                elif str(line[2]) == "2" and str(line[0]) != str(json_obj['data']['option_id']):
                    json_str = json.JSONEncoder().encode(
                        {
                            "packet_type": "knx_write_short",
                            "addr_to_send": line[1],
                            "value": "0"
                        }
                    );
                    self.send_json_obj_to_slave(json_str, sock, hostname, self.aes_slave_keys[hostname]);
        json_str = json.JSONEncoder().encode(
            {
                "packet_type": "knx_write_short",
                "addr_to_send": str(dev['addr_dst']),
                "value": json_obj['data']['value']
            }
        );
        self.send_json_obj_to_slave(json_str, sock, hostname, self.aes_slave_keys[hostname]);
        sock.close();

    ## Converts absolute value of temperature (Celsius) in 2 hexadecimal values for sending to KNX device.
    #
    # @param json_obj JSON object containing the values to write.
    # @param dev Object describing the KNX device to who send the packet.
    # @param hostname The hostname of the slave daemon to who send the packet.
    # @return None
    def send_knx_write_temp(self, json_obj, dev, hostname):
        port = self._parser.getValueFromSection('connect', 'port');
        if not port:
            sys.exit(4);
        sock = socket.create_connection((hostname, port));
        val_str = json_obj['data']['value'];
        if ',' in val_str:
            val_str = val_str.replace(',', '.')
        value = utils.convert_temperature_reverse(float(val_str));
        json_str = json.JSONEncoder().encode(
            {
                "packet_type": "knx_write_temp",
                "addr_to_send": str(dev['addr_dst']),
                "value": value[0] + ' ' + value[1]
            }
        );
        self.send_json_obj_to_slave(json_str, sock, hostname, self.aes_slave_keys[hostname]);
        sock.close();

    ## Builds a "long write" request and sends it to "hostname".
    #
    # @param json_obj JSON object containing the new values.
    # @param dev The device to who send the request.
    # @param hostname The slave daemon to who send the packet.
    # @return None
    def send_knx_write_long_to_slave(self, json_obj, dev, hostname):
        port = self._parser.getValueFromSection('connect', 'port');
        if not port:
            sys.exit(4);
        sock = socket.create_connection((hostname, port));
        json_str = json.JSONEncoder().encode(
            {
                "packet_type": "knx_write_long",
                "addr_to_send": str(dev['addr_dst']),
                "value": hex(int(json_obj['data']['value']))
            }
        );
        self.send_json_obj_to_slave(json_str, sock, hostname, self.aes_slave_keys[hostname]);
        sock.close();

    ## Builds a "short read" request and sends it to "hostname".
    #
    # @param json_obj JSON object containing the new values.
    # @param dev The device to who send the request.
    # @param hostname The hostname of the slave daemon to who send the packet.
    # @return None
    def send_knx_write_short_to_slave(self, json_obj, dev, hostname):
        port = self._parser.getValueFromSection('connect', 'port');
        if not port:
            sys.exit(4);
        sock = socket.create_connection((hostname, port));
        json_str = json.JSONEncoder().encode(
            {
                "packet_type": "knx_write_short",
                "addr_to_send": str(dev['addr_dst']),
                "value": json_obj['data']['value']
            }
        );
        self.send_json_obj_to_slave(json_str, sock, hostname, self.aes_slave_keys[hostname]);
        sock.close();

    ## Changes the value to send when it is supposed to be inverted and sends the packet to the slave.
    #
    # @param json_obj JSON object to change before sending.
    # @param dev The device to who send the request.
    # @param hostname The hostname of the slave daemon to who send the packet.
    # @return None.
    def send_knx_write_short_to_slave_reverse(self, json_obj, dev, hostname):
        json_obj['data']['value'] = (int(json_obj['data']['value']) + 1) % 2;
        self.send_knx_write_short_to_slave(json_obj, dev, hostname);
        
    ## Builds a "short read" request and sends it to the slave "hostname".
    #
    # @param hostname The slave daemon to who send the read request.
    # @param json_obj JSON object containing the data to send.
    # @return None
    def send_knx_read_request_to_slave(self, hostname, json_obj):
        port = self._parser.getValueFromSection('connect', 'port');
        if not port:
            sys.exit(4);
        sock = socket.create_connection((hostname, port));
        json_str = json.JSONEncoder().encode(
            {
                "packet_type": "knx_read_request",
                "addr_to_read": json_obj['data']['addr']
            }
        );
        self.send_json_obj_to_slave(json_str, sock, hostname, self.aes_slave_keys[hostname]);
        sock.close();

    ## Asks to send a "on" packet to a device.
    #
    # @param json_obj JSON object containing the new values.
    # @param dev The KNX device.
    # @param hostname The hostname of the slave daemon.
    # @return None
    def send_on(self, json_obj, dev, hostname):
        port = self._parser.getValueFromSection('connect', 'port');
        if not port:
            sys.exit(4);
        sock = socket.create_connection((hostname, port));
        json_str = json.JSONEncoder().encode(
            {
                "packet_type": "knx_write_short",
                "addr_to_send": str(dev['addr_dst']),
                "value": "1"
            }
        );
        self.send_json_obj_to_slave(json_str, sock, hostname, self.aes_slave_keys[hostname]);
        sock.close();
        return;

    ## Asks to send a "off" packet to a device.
    #
    # @param json_obj JSON object containing the new values.
    # @param dev The KNX device.
    # @param hostname The hostname of the slave daemon.
    # @return None
    def send_off(self, json_obj, dev, hostname):
        port = self._parser.getValueFromSection('connect', 'port');
        if not port:
            sys.exit(4);
        sock = socket.create_connection((hostname, port));
        json_str = json.JSONEncoder().encode(
            {
                "packet_type": "knx_write_short",
                "addr_to_send": str(dev['addr_dst']),
                "value": "0"
            }
        );
        self.send_json_obj_to_slave(json_str, sock, hostname, self.aes_slave_keys[hostname]);
        sock.close();
        return;

    ## Sends the new value of the temperature to thermostat.
    #
    # @param json_obj JSON object containing the new values.
    # @param dev The KNX device.
    # @param hostname The hostname of the slave daemon.
    # @return None
    def send_to_thermostat(self, json_obj, dev, hostname):
        port = self._parser.getValueFromSection('connect', 'port');
        if not port:
            sys.exit(4);
        if json_obj['data']['option_id'] == '412':
            val = 1;
        elif json_obj['data']['option_id'] == '413':
            val = 2;
        elif json_obj['data']['option_id'] == '414':
            val = 4;
        elif json_obj['data']['option_id'] == '415':
            val = 8;
        elif json_obj['data']['option_id'] == '416':
            val = 16;
        elif json_obj['data']['option_id'] == '417':
            val = 32;
        else:
            val = 0
        if val > 0:
            json_str = json.JSONEncoder().encode(
                {
                    "packet_type": "knx_write_long",
                    "addr_to_send": hex(int(dev['addr_dst'])),
                    "value": val
                }
            );
            sock = socket.create_connection((hostname, port));
            self.send_json_obj_to_slave(json_str, sock, hostname, self.aes_slave_keys[hostname]);
            sock.close();
            return;

    ## Sends the new mode of the air conditioner.
    #
    # @param json_obj JSON object containing the new values.
    # @param dev The KNX device.
    # @param hostname The hostname of the slave daemon.
    # @return None
    def send_clim_mode(self, json_obj, dev, hostname):
        if json_obj['data']['option_id'] == '425': #Auto
            val = 0
        elif json_obj['data']['option_id'] == '426': #Heat
            val = 1
        elif json_obj['data']['option_id'] == '427': #Morning Warmup
            val = 2
        elif json_obj['data']['option_id'] == '428': #Cool
            val = 3
        elif json_obj['data']['option_id'] == '429': #Night Purge
            val = 4
        elif json_obj['data']['option_id'] == '430': #Precool
            val = 5
        elif json_obj['data']['option_id'] == '431': #Off
            val = 6
        elif json_obj['data']['option_id'] == '432': #Test
            val = 7
        elif json_obj['data']['option_id'] == '433': #Emergency Heat
            val = 8
        elif json_obj['data']['option_id'] == '434': #Fan only
            val = 9
        elif json_obj['data']['option_id'] == '435': #Free Cool
            val = 10
        elif json_obj['data']['option_id'] == '436': #Ice
            val = 11
        else:
            val = -1
        if val >= 0:
            json_str = json.JSONEncoder().encode(
                {
                    "packet_type": "knx_write_long",
                    "addr_to_send": str(dev['addr_dst']),
                    "value":  hex(int(val))
                }
            );
            sock = socket.create_connection((hostname, port));
            self.send_json_obj_to_slave(json_str, sock, hostname, self.aes_slave_keys[hostname]);
            sock.close();
            return;

    ## Sends a value converted in percentages.
    #
    # @param json_obj Not used here.
    # @param dev Object describing the KNX device.
    # @param hostname Hostname of the slave daemon to who sent the packet.
    # @return None
    def send_knx_write_percent(self, json_obj, dev, hostname):
        port = self._parser.getValueFromSection('connect', 'port');
        if not port:
            sys.exit(4);
        sock = socket.create_connection((hostname, port));
        json_str = json.JSONEncoder().encode(
            {
                "packet_type": "knx_write_long",
                "addr_to_send": str(dev['addr_dst']),
                "value": hex(int(255*int(json_obj['data']['value'])/100))
            }
        );
        self.send_json_obj_to_slave(json_str, sock, hostname, self.aes_slave_keys[hostname]);
        sock.close();
Example #17
0
class SlaveDaemon:

    ## The constructor
    #
    # @param log_flag The flag saying whether the logs should be printing or not
    def __init__(self, log_flag):
        ## Logger object for formatting and printing logs
        self.logger = Logger(log_flag, LOG_FILE)
        self.logger.info("Started Domoleaf Slave daemon")
        ## Array of master daemon on local network
        self.connected_masters = {}
        ## Array of monitor KNX on local network
        self.connected_knx = []
        ## Array of monitor EnOcean on local network
        self.connected_enocean = []
        ## Array of cron running on the system
        self.connected_cron = []
        self._scanner = Scanner(log_flag)
        self._hostlist = []
        myhostname = socket.gethostname().upper()
        if SLAVE_NAME_PREFIX in myhostname:
            self._scanner.scan()
            self._hostlist = self._scanner._HostList
        else:
            self._hostlist.append(Host("", "127.0.0.1", myhostname))
        self._parser = DaemonConfigParser(SLAVE_CONF_FILE)
        ## Keys for encrypting communications
        self.encrypt_keys = {}
        ## Main socket for communication with KNX daemon
        self.knx_sock = None
        ## Main socket for communication with master daemon
        self.master_sock = None
        ## Main socket for communication with enocean daemon
        self.enocean_sock = None
        ## Main socket for communication with cron
        self.cron_sock = None
        ## Private AES key got from configuration file
        self.private_aes = self._parser.getValueFromSection("personnal_key", "aes")
        self.wifi_init(
            self._parser.getValueFromSection("wifi", "ssid"),
            self._parser.getValueFromSection("wifi", "password"),
            self._parser.getValueFromSection("wifi", "encryption"),
            self._parser.getValueFromSection("wifi", "mode"),
            0,
        )
        ## Port on which connect got from configuration file
        self.connect_port = self._parser.getValueFromSection(SLAVE_CONF_CONNECT_SECTION, SLAVE_CONF_CONNECT_PORT_ENTRY)
        ## Callback array indexed on packet type
        self.functions = {
            KNX_READ_REQUEST: self.knx_read_request,
            KNX_WRITE_SHORT: self.knx_write_short,
            KNX_WRITE_LONG: self.knx_write_long,
            KNX_WRITE_TEMP: self.knx_write_temp,
            CHECK_SLAVE: self.check_slave,
            MONITOR_IP: self.monitor_ip,
            DATA_UPDATE: self.update,
            SEND_TECH: self.send_tech,
            SEND_ALIVE: self.send_alive,
            SEND_INTERFACES: self.send_interfaces,
            SHUTDOWN_D3: self.shutdown_d3,
            REBOOT_D3: self.reboot_d3,
            WIFI_UPDATE: self.wifi_update,
        }

    ## Updates the base system of the slave daemon.
    #
    # @param json_obj Not used here.
    # @param connection Not used here.
    # @return None
    def update(self, json_obj, connection):
        p = call(["dpkg", "--configure", "-a"])
        call(["apt-get", "update"])
        call(["DEBIAN_FRONTEND=noninteractive", "apt-get", "install", "domoslave", "-y"])
        version = os.popen("dpkg-query -W -f='${Version}\n' domoslave").read().split("\n")[0]
        json_str = (
            '{"packet_type": "update_finished", "aes_pass": "******", "new_version": ' + version + "}"
        )
        encrypt_IV = AESManager.get_IV()
        spaces = 16 - len(json_str) % 16
        json_str = json_str + (spaces * " ")
        encode_obj = AES.new(self.private_aes, AES.MODE_CBC, encrypt_IV)
        data = encode_obj.encrypt(json_str)
        # faut ouvrir une nouvelle socket pour envoyer la nouvelle version
        # connection.send(bytes(encrypt_IV, 'utf-8') + data);

    ## Initializes the sockets for listenning incomming connections.
    #
    # @return None
    def run(self):
        ## Run flag at True for running, at False to stop the main loop
        self.run = True
        self.knx_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.master_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.enocean_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.cron_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.knx_sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
        self.master_sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
        self.enocean_sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
        self.cron_sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
        port = self._parser.getValueFromSection(SLAVE_CONF_KNX_SECTION, SLAVE_CONF_KNX_PORT_ENTRY)
        if not port:
            sys.exit(2)
        port_master = self._parser.getValueFromSection(SLAVE_CONF_LISTEN_SECTION, SLAVE_CONF_LISTEN_PORT_ENTRY)
        if not port_master:
            sys.exit(2)
        port_enocean = self._parser.getValueFromSection(SLAVE_CONF_ENOCEAN_SECTION, SLAVE_CONF_ENOCEAN_PORT_ENTRY)
        if not port_enocean:
            sys.exit(2)
        port_cron = self._parser.getValueFromSection(SLAVE_CONF_CRON_SECTION, SLAVE_CONF_CRON_PORT_ENTRY)
        if not port_cron:
            sys.exit(2)
        self.knx_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.master_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.enocean_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.cron_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.knx_sock.bind(("", int(port)))
        self.master_sock.bind(("", int(port_master)))
        self.enocean_sock.bind(("", int(port_enocean)))
        self.cron_sock.bind(("127.0.0.1", int(port_cron)))
        self.knx_sock.listen(MAX_KNX)
        self.master_sock.listen(MAX_MASTERS)
        self.enocean_sock.listen(MAX_ENOCEAN)
        self.cron_sock.listen(MAX_CRON)
        self.send_monitor_ip()
        self.loop()

    ## Gets available sockets for reading on the KNX socket.
    #
    # @return None
    def accept_knx(self):
        rlist, wlist, elist = select.select([self.knx_sock], [], [], SELECT_TIMEOUT)
        append = self.connected_knx.append
        for connection in rlist:
            new_knx, addr = connection.accept()
            append(new_knx)
        self.receive_from_knx(self.connected_knx)

    ## Gets available sockets for reading on the master socket.
    #
    # @return None
    def accept_masters(self):
        rlist, wlist, elist = select.select([self.master_sock], [], [], SELECT_TIMEOUT)
        masters_socks = []
        append = masters_socks.append
        for item in rlist:
            new_conn, addr = item.accept()
            append(new_conn)
        self.receive_from_masters(masters_socks)

    ## Gets available sockets for reading on the EnOcean socket.
    #
    # @return None
    def accept_enocean(self):
        rlist, wlist, elist = select.select([self.enocean_sock], [], [], SELECT_TIMEOUT)
        enocean_socks = []
        append = enocean_socks.append
        append_connected = self.connected_enocean.append
        for item in rlist:
            new_conn, addr = item.accept()
            append(new_conn)
            append_connected(new_conn)
        self.receive_from_enocean(enocean_socks)

    ## Gets available sockets for reading on the Cron socket.
    #
    # @return None
    def accept_cron(self):
        rlist, wlist, elist = select.select([self.cron_sock], [], [], SELECT_TIMEOUT)
        cron_socks = []
        append = cron_socks.append
        append_connected = self.connected_cron.append
        for item in rlist:
            new_conn, addr = item.accept()
            append(new_conn)
            append_connected(new_conn)
        self.receive_from_cron(cron_socks)

    ## Checks the packet_type of the data, and calls the appropriate function.
    #
    # @param data Packet data including the packet type.
    # @param connection Connection object sent to the function.
    # @return None
    def parse_data(self, data, connection):
        json_obj = json.JSONDecoder().decode(data)
        if json_obj["packet_type"] in self.functions.keys():
            self.functions[json_obj["packet_type"]](json_obj, connection)
        else:
            raise Exception(str(json_obj["packet_type"]) + ": is not a valid packet type")

    ## System call of 'groupread' with parameters.
    #
    # @param json_obj JSON Object containing the address to read.
    # @param connection Not used here.
    # @return None
    def knx_read_request(self, json_obj, connection):
        call(["knxtool", CALL_GROUPREAD, EIB_URL, json_obj["addr_to_read"]])

    ## System call of "groupwrite" with parameters.
    #
    # @param json_obj JSON Object containing the address to which send, and the values to send.
    # @param connection Not used here.
    # @return None
    def knx_write_temp(self, json_obj, connection):
        val = json_obj["value"].split(" ")
        call(["knxtool", CALL_GROUPWRITE, EIB_URL, json_obj["addr_to_send"], val[0], val[1]])

    ## System call of "groupswrite" with parameters.
    #
    # @param json_obj JSON Object containing the address to which send, and the value to send.
    # @param connection Not used here.
    # @return None
    def knx_write_short(self, json_obj, connection):
        call(["knxtool", CALL_GROUPSWRITE, EIB_URL, json_obj["addr_to_send"], str(json_obj["value"])])

    ## System call of "groupwrite" with parameters.
    #
    # @param json_obj JSON Object containing the address to which send, and the value to send.
    # @param connection Not used here.
    # @return None
    def knx_write_long(self, json_obj, connection):
        call(["knxtool", CALL_GROUPWRITE, EIB_URL, json_obj["addr_to_send"], str(json_obj["value"])])

    ## Reads data comming from amsters and calls parse_data().
    #
    # @param masters_to_read Array containing the sockets of all the masters found on local network.
    # @return None
    def receive_from_masters(self, masters_to_read):
        for master in masters_to_read:
            data = master.recv(4096)
            decrypt_IV = data[:16].decode()
            decode_obj = AES.new(self.private_aes, AES.MODE_CBC, decrypt_IV)
            data2 = decode_obj.decrypt(data[16:])
            self.parse_data(data2.decode(), master)
            master.close()

    ## Reads data from monitor KNX and transmits to master.
    #
    # @param knx_to_read Array containing the sockets of all the KNX daemons on local network.
    # @return None
    def receive_from_knx(self, knx_to_read):
        for knx in knx_to_read:
            data = knx.recv(TELEGRAM_LENGTH)
            if data:
                self.send_knx_data_to_masters(data)
            if knx in self.connected_knx:
                knx.close()
                self.connected_knx.remove(knx)

    ## Reads data from monitor EnOcean and transmits to master.
    #
    # @param enocean_to_read Array containing the sockets of all the EnOcean daemons on local network.
    # @return None
    def receive_from_enocean(self, enocean_to_read):
        for enocean in enocean_to_read:
            data = enocean.recv(4096)
            if data:
                self.send_enocean_data_to_masters(data)
            if enocean in self.connected_enocean:
                enocean.close()
                self.connected_enocean.remove(enocean)

    ## Receives data from a cron and executes it.
    #
    # @param cron_to_read Array containing the sockets of all crons on local network.
    # @return None
    def receive_from_cron(self, cron_to_read):
        for cron in cron_to_read:
            data = cron.recv(4096)
            if data:
                json_str = json.JSONEncoder().encode({"packet_type": data.decode()})
                self.parse_data(json_str, cron)
            if cron in self.connected_cron:
                cron.close()
                self.connected_cron.remove(cron)

    ## Checks the existence of this daemon.
    # This function is called when a check_slave packet is received.
    #
    # @param json_obj JSON Object containing the hostname of the sender of the packet.
    # @param connection Connection object used to send the response.
    # @return None
    def check_slave(self, json_obj, connection):
        interface_knx = self._parser.getValueFromSection(SLAVE_CONF_KNX_SECTION, SLAVE_CONF_KNX_INTERFACE)
        interface_enocean = self._parser.getValueFromSection(SLAVE_CONF_ENOCEAN_SECTION, SLAVE_CONF_ENOCEAN_INTERFACE)
        version = os.popen("dpkg-query -W -f='${Version}\n' domoslave").read().split("\n")[0]
        json_str = (
            '{"packet_type": "check_slave", "aes_pass": "******", "version": "'
            + version
            + '", "interface_knx": "'
            + interface_knx
            + '", "interface_enocean": "'
            + interface_enocean
            + '"}'
        )
        master_hostname = str(json_obj["sender_name"])
        encrypt_IV = AESManager.get_IV()
        spaces = 16 - len(json_str) % 16
        json_str = json_str + (spaces * " ")
        encode_obj = AES.new(self.private_aes, AES.MODE_CBC, encrypt_IV)
        data = encode_obj.encrypt(json_str)
        connection.send(bytes(encrypt_IV, "utf-8") + data)

    ## Re scans the local network and refreshes hostlist.
    #
    # @param json_obj Not used here.
    # @param connection Not used here.
    # @return None
    def monitor_ip(self, json_obj, connection):
        self._scanner.scan()
        self._hostlist = self._scanner._HostList

    ## Sends a monitor_ip packet to all the masters available.
    #
    # @return None
    def send_monitor_ip(self):
        json_str = json.JSONEncoder().encode({"packet_type": "monitor_ip"})
        self.send_data_to_all_masters(json_str)

    ## Main daemon loop.
    #
    # @return None
    def loop(self):
        while self.run:
            try:
                self.accept_knx()
            except Exception as e:
                frameinfo = getframeinfo(currentframe())
                self.logger.error("in loop accept_knx: " + str(e))
            except KeyboardInterrupt as e:
                frameinfo = getframeinfo(currentframe())
                self.logger.error("in loop: Keyboard interrupt")
            try:
                self.accept_masters()
            except Exception as e:
                frameinfo = getframeinfo(currentframe())
                self.logger.error("in loop accept_masters: " + str(e))
            except KeyboardInterrupt as e:
                frameinfo = getframeinfo(currentframe())
                self.logger.error("in loop: Keyboard interrupt")
            try:
                self.accept_enocean()
            except Exception as e:
                frameinfo = getframeinfo(currentframe())
                self.logger.error("in loop accept_enocean: " + str(e))
            except KeyboardInterrupt as e:
                frameinfo = getframeinfo(currentframe())
                self.logger.error("in loop: Keyboard interrupt")
            try:
                self.accept_cron()
            except Exception as e:
                frameinfo = getframeinfo(currentframe())
                self.logger.error("in loop accept_cron: " + str(e))
            except KeyboardInterrupt as e:
                frameinfo = getframeinfo(currentframe())
                self.logger.error("in loop: Keyboard interrupt")

    ## Stops the daemon and closes all sockets.
    #
    # @return None
    def stop(self):
        for name, sock in self.connected_masters.items():
            sock.close()
        for knx in self.connected_knx:
            knx.close()
        self.knx_sock.close()

    ## Stores every host on local network if its hostname begins by 'MD3' in connected_masters dict().
    #
    # @return None
    def connect_to_masters(self):
        hostname = socket.gethostname()
        self.connected_masters = {}
        for host in self._hostlist:
            if MASTER_NAME_PREFIX in host._Hostname or str(host._IpAddr) == "127.0.0.1":
                if not self.connect_port:
                    self.logger.error(
                        "in connect_to_masters: No "
                        + SLAVE_CONF_CONNECT_PORT_ENTRY
                        + " in "
                        + SLAVE_CONF_CONNECT_SECTION
                        + " section or maybe no such "
                        + SLAVE_CONF_CONNECT_SECTION
                        + " defined"
                    )
                    sys.exit(1)
                try:
                    self.logger.debug("Connecting to " + str(host._IpAddr) + ":" + str(self.connect_port))
                    sock = socket.create_connection((host._IpAddr, self.connect_port))
                    hostname = host._Hostname.split(".")[0]
                    self.connected_masters[host._Hostname] = sock
                except Exception as e:
                    frameinfo = getframeinfo(currentframe())
                    self.logger.error("in connect_to_masters: " + str(e))
                    pass

    ## Converts data from bytes to a clear KNX datagram, and sends it to all available masters.
    #
    # @param data The data having to be converted from bytes to clear KNX datagram.
    # @return None
    def send_knx_data_to_masters(self, data):
        ctrl = int(data[0])
        src_addr = int.from_bytes(data[1:3], byteorder="big")
        dst_addr = int.from_bytes(data[3:5], byteorder="big")
        data_len = int.from_bytes(data[5:6], byteorder="big")
        telegram_data = data[6 : 7 + data_len]
        typ = -1
        value = 0
        if telegram_data[1] & 0xC0 == 0x00:  # read
            typ = 0
        elif telegram_data[1] & 0xC0 == 0x40:  # resp
            typ = 1
            if data_len == 2:
                value = int(telegram_data[1] & 0x0F)
            elif data_len > 2:
                value = int.from_bytes(telegram_data[2:data_len], byteorder="big")
        elif telegram_data[1] & 0xC0 == 0x80:  # write
            typ = 2
            if data_len == 2:
                value = int(telegram_data[1] & 0x0F)
            elif data_len > 2:
                typ = 3
                value = int.from_bytes(telegram_data[2:data_len], byteorder="big")
        json_str = json.JSONEncoder().encode(
            {
                "packet_type": "monitor_knx",
                "type": typ,
                "src_addr": individual2string(src_addr),
                "dst_addr": group2string(dst_addr),
                "date": str(time.time()).split(".")[0],
                "value": value,
                "sender_name": socket.gethostname(),
            }
        )
        self.send_data_to_all_masters(json_str)

    ## Convertes data from bytes to a clear EnOcean datagram, and sends it to available masters.
    #
    # @param data The data having to be converted from bytes to EnOcean datagram.
    # @return None
    def send_enocean_data_to_masters(self, data):
        if data[4] == PACKET_TYPE_RADIO_ERP1:  # si le packet_type == radio_erp1
            data_len = int.from_bytes(data[1:2], byteorder="big")
            opt_data_len = int(data[3])
            src_str = "%X" % int.from_bytes(data[1 + data_len : 5 + data_len], byteorder="big")
            if len(src_str) < 8:
                src_str = "0" + src_str
            json_dict = {
                "packet_type": "monitor_enocean",
                "src_addr": src_str,
                "dst_addr": "%X" % int.from_bytes(data[261 : 265 + opt_data_len], byteorder="big"),
                "date": str(time.time()).split(".")[0],
                "sender_name": socket.gethostname(),
                "type": int(data[6]),
            }
            if data[6] == RORG_NORMAL:
                json_dict["value"] = int(data[7])
            elif data[6] == RORG_TEMPERATURE:
                json_dict["value"] = float(40 - ((40 / 255) * int(data[9])))
            json_str = json.JSONEncoder().encode(json_dict)
            self.send_data_to_all_masters(json_str)

    ## Sends data to all masters available on local network.
    #
    # @param json_str The data to send under form of a JSON Object stringified.
    # @return None
    def send_data_to_all_masters(self, json_str):
        self.connect_to_masters()
        for name in self.connected_masters.keys():
            try:
                master = self.connected_masters[name]
                AES.key_size = 32
                aes_IV = AESManager.get_IV()
                encode_obj = AES.new(self.private_aes, AES.MODE_CBC, aes_IV)
                spaces = 16 - len(json_str) % 16
                data2 = encode_obj.encrypt(json_str + (spaces * " "))
                master.send(bytes(aes_IV, "utf-8") + data2)
                master.close()
            except KeyError as e:
                self.logger.error("in send_data_to_all_masters: " + str(e))
                pass

    ## Sends the informations about the slave to all masters available.
    #
    # @param json_obj Not used here.
    # @param connection Not used here.
    # @return None
    def send_tech(self, json_obj, connection):
        json_str = json.JSONEncoder().encode({"packet_type": "send_tech", "info": GLManager.TechInfo()})
        self.send_data_to_all_masters(json_str)

    ## Sends that the salve daemon is alive to all masters available.
    #
    # @param json_obj Not used here.
    # @param connection Not used here.
    # @return None
    def send_alive(self, json_obj, connection):
        json_str = json.JSONEncoder().encode({"packet_type": "send_alive", "info": GLManager.TechAlive()})
        self.send_data_to_all_masters(json_str)

    ## Sends the protocol interface informations to all masters available.
    #
    # @param json_obj Protocol interface informations.
    # @param connection Connection object used to send the response.
    # @return None
    def send_interfaces(self, json_obj, connection):
        try:
            if os.path.exists("/tmp/knxd"):
                call(["service", "knxd", "stop"])
            previous_val_knx = self._parser.getValueFromSection("knx", "interface")
            previous_val_EnOcean = self._parser.getValueFromSection("enocean", "interface")
            new_val = str(json_obj["interface_arg_knx"])
            self._parser.writeValueFromSection("knx", "interface", new_val)
            self._parser.writeValueFromSection("knx", "activated", str(json_obj["daemon_knx"]))
            self._parser.writeValueFromSection("enocean", "interface", str(json_obj["interface_arg_EnOcean"]))
            if not previous_val_knx or previous_val_knx is None:
                call(["update-rc.d", "knxd", "defaults"])
                call(["update-rc.d", "knxd", "enable"])
            if not new_val or new_val is None:
                Popen(["systemctl", "-q", "disable", "knxd"])
            else:
                knx_edit = 'KNXD_OPTS="-e 1.0.254 -D -T -S -b '
                if json_obj["interface_knx"] == "tpuarts":
                    knx_edit += json_obj["interface_knx"] + ":/dev/" + new_val + '"'
                else:
                    knx_edit += json_obj["interface_knx"] + ":" + new_val + '"'
                conf_knx = open("/etc/knxd.conf", "w")
                conf_knx.write(knx_edit + "\n")
                conf_knx.close()
                call(["service", "knxd", "start"])
                if json_obj["daemon_knx"] == 1:
                    if os.path.exists("/var/run/monitor_knx.pid"):
                        os.remove("/var/run/monitor_knx.pid")
                    Popen(["monitor_knx", "ip:localhost", "--daemon"])
        except Exception as e:
            self.logger.error(e)
        json_str = '{"packet_type": "send_interfaces", "aes_pass": "******"}'
        master_hostname = str(json_obj["sender_name"])
        encrypt_IV = AESManager.get_IV()
        spaces = 16 - len(json_str) % 16
        json_str = json_str + (spaces * " ")
        encode_obj = AES.new(self.private_aes, AES.MODE_CBC, encrypt_IV)
        data = encode_obj.encrypt(json_str)
        connection.send(bytes(encrypt_IV, "utf-8") + data)
        if previous_val_EnOcean != str(json_obj["interface_arg_EnOcean"]):
            call(["service", "domoslave", "restart"])

    ## Shuts down the slave D3.
    #
    # @param json_obj Not used here.
    # @param connection Not used here.
    # @return None
    def shutdown_d3(self, json_obj, connection):
        call(["poweroff"])

    ## Reboots the slave D3.
    #
    # @param json_obj Not used here.
    # @param connection Not used here.
    # @return None
    def reboot_d3(self, json_obj, connection):
        call(["reboot"])

    ## Initializes the wifi protocol.
    #
    # @param ssid The SSID of the network.
    # @param password The password to connect to the network.
    # @param security The security type of the connection.
    # @param mode The mode of the initialization of the network interface.
    # @param opt Flag to do some more stuff if it is 1.
    # @return None
    def wifi_init(self, ssid, password, security, mode, opt):
        try:
            ps_process = Popen(["ps", "-x"], stdout=PIPE)
            res = Popen(["grep", "hostapd"], stdin=ps_process.stdout, stdout=PIPE)
            res = res.stdout.read().decode().split("\n")[0].split(" ")
            ps_process.stdout.close()
            if res:
                while "" in res:
                    res.remove("")
                call(["kill", "-9", res[0]])
            ps_process = Popen(["ps", "-x"], stdout=PIPE)
            res = Popen(["grep", "wpa_supplicant"], stdin=ps_process.stdout, stdout=PIPE)
            res = res.stdout.read().decode().split("\n")[0].split(" ")
            ps_process.stdout.close()
            if res:
                while "" in res:
                    res.remove("")
                call(["kill", "-9", res[0]])
            call(["ifconfig", "wlan0", "down"])
            if mode == WIFI_MODE_DISABLED:
                if opt == 1:
                    call(["service", "dnsmasq", "stop"])
            elif mode == WIFI_MODE_CLIENT:
                call(["ifconfig", "wlan0", "up"])
                if opt == 1:
                    call(["service", "dnsmasq", "stop"])
                conf_file = open("/etc/network/interfaces", "w")
                conf_str = "".join(
                    [
                        "auto lo\niface lo inet loopback\n\nallow-hotplug eth0\n",
                        "iface eth0 inet dhcp\n\nallow-hotplug usb0\niface usb0 inet dhcp\n\n",
                        "auto wlan0\niface wlan0 inet dhcp\n\twpa-conf ",
                        WPA_SUPPLICANT_CONF_FILE,
                        "\n",
                    ]
                )
                conf_file.write(conf_str)
                conf_file.close()
                conf_file = open(WPA_SUPPLICANT_CONF_FILE, "w")
                conf_str = "".join(
                    [
                        "ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev\n",
                        "update_config=1\nctrl_interface_group=0\neapol_version=1\n",
                        "ap_scan=1\n fast_reauth=1\n\n\nnetwork={\n\tdisabled=0\n",
                        '\tssid="',
                        ssid,
                        '"\n\tscan_ssid=0\n\tpriority=1\n',
                    ]
                )
                if security == WIFI_SECURITY_WPA:
                    conf_str += (
                        "\tproto=WPA\n\tkey_mgmt=WPA-PSK\n\tauth_alg=OPEN\n"
                        + '\tpairwise=TKIP CCMP\n\tgroup=TKIP CCMP\n\tpsk="'
                        + password
                        + '"\n'
                    )
                elif security == WIFI_SECURITY_WPA2:
                    conf_str += (
                        "\tproto=RSN\n\tkey_mgmt=WPA-PSK\n\tauth_alg=OPEN\n\tpairwise=CCMP TKIP\n"
                        + '\tgroup=CCMP TKIP\n\tpsk="'
                        + password
                        + '"\n'
                    )
                elif security == WIFI_SECURITY_WEP:
                    conf_str += "\tkey_mgmt=NONE\n\tauth_alg=SHARED\n"
                    if len(password) == 5 or len(password) == 10:
                        conf_str += "\tgroup=WEP40\n"
                    elif len(password) == 13 or len(password) == 26:
                        conf_str += "\tgroup=WEP104\n"
                    else:
                        conf_str += "\tgroup=WEP40 WEP104\n"
                    conf_str += '\twep_key0="' + password + '"\n\twep_tx_keyidx=0\n'
                conf_str += "\tpriority=1\n}\n"
                conf_file.write(conf_str)
                conf_file.close()
                call(["wpa_supplicant", "-Dnl80211", "-iwlan0", "-c" + WPA_SUPPLICANT_CONF_FILE, "-B"])
                call(["dhclient", "wlan0"])
            elif mode == WIFI_MODE_ACCESS_POINT:
                call(["ifconfig", "wlan0", "172.16.0.1", "netmask", "255.255.255.0", "up"])
                conf_file = open(HOSTAPD_CONF_FILE, "w")
                conf_str = "".join(
                    [
                        "interface=wlan0\n\ndriver=nl80211\n\nssid=",
                        ssid,
                        "\n\n",
                        "hw_mode=g\n\nieee80211n=1\n\nchannel=6\n\nbeacon_int=100\n\n",
                        "dtim_period=2\n\nmax_num_sta=255\n\nrts_threshold=2347\n\n",
                        "fragm_threshold=2346\n\nmacaddr_acl=0\n\n",
                    ]
                )
                if security == WIFI_SECURITY_WPA:
                    conf_str += (
                        "auth_algs=1\n\nwpa=1\n\nwpa_passphrase="
                        + password
                        + "\n\n"
                        + "wpa_key_mgmt=WPA-PSK\n\nwpa_pairwise=TKIP\n"
                    )
                elif security == WIFI_SECURITY_WPA2:
                    conf_str += (
                        "auth_algs=1\n\nwpa=2\n\nwpa_passphrase="
                        + password
                        + "\n\n"
                        + "wpa_key_mgmt=WPA-PSK\n\nwpa_pairwise=CCMP\n\nrsn_pairwise=CCMP\n"
                    )
                else:
                    self.logger.error("Wifi security = Unknown")
                conf_file.write(conf_str)
                conf_file.close()
                if opt == 1:
                    conf_file = open(DNSMASQ_CONF_FILE, "w")
                    conf_str = "domain-needed\ninterface=wlan0\ndhcp-range=172.16.0.2,172.16.0.254,12h\n"
                    conf_file.write(conf_str)
                    conf_file.close()
                    call(["service", "dnsmasq", "restart"])
                call(["iptables", "-t", "nat", "-A", "POSTROUTING", "-j", "MASQUERADE"])
                call(["hostapd", HOSTAPD_CONF_FILE, "-B"])
            else:
                call(["ifconfig", "wlan0", "up"])
                self.logger.error("Wifi mode = Unknown")
        except Exception as e:
            self.logger.error(e)

    ## Updates the wifi informations.
    #
    # @param json_obj JSON Object containing all the informations for the wifi.
    # @param connection Connection object used to send the response.
    # @return None
    def wifi_update(self, json_obj, connection):
        try:
            self._parser.writeValueFromSection("wifi", "ssid", json_obj["ssid"])
            self._parser.writeValueFromSection("wifi", "password", json_obj["password"])
            self._parser.writeValueFromSection("wifi", "encryption", json_obj["security"])
            self._parser.writeValueFromSection("wifi", "mode", json_obj["mode"])
            self.wifi_init(json_obj["ssid"], json_obj["password"], json_obj["security"], json_obj["mode"], 1)
        except Exception as e:
            self.logger.error(e)
        json_str = '{"packet_type": "wifi_update", "aes_pass": "******"}'
        master_hostname = str(json_obj["sender_name"])
        encrypt_IV = AESManager.get_IV()
        spaces = 16 - len(json_str) % 16
        json_str = json_str + (spaces * " ")
        encode_obj = AES.new(self.private_aes, AES.MODE_CBC, encrypt_IV)
        data = encode_obj.encrypt(json_str)
        connection.send(bytes(encrypt_IV, "utf-8") + data)
Example #18
0
class MasterSql:

    ## The constructor
    #
    # @param log_flag Flag saying if the logs are active or not.
    def __init__(self, log_flag = False):
        ## Logger object for formatting and printing logs
        self.logger = Logger(log_flag, LOG_FILE);
        self._parser = DaemonConfigParser(MASTER_CONF_FILE);
        ## Username for the database, searched in configuration file
        self.db_username = self._parser.getValueFromSection(MASTER_CONF_MYSQL_SECTION,
                                                            MASTER_CONF_MYSQL_USER_ENTRY);
        ## Password for the database, searched in configuration file
        self.db_passwd = self._parser.getValueFromSection(MASTER_CONF_MYSQL_SECTION,
                                                          MASTER_CONF_MYSQL_PASSWORD_ENTRY);
        ## Database name for the database, searched in configuration file
        self.db_dbname = self._parser.getValueFromSection(MASTER_CONF_MYSQL_SECTION,
                                                          MASTER_CONF_MYSQL_DB_NAME_ENTRY);
        if not self.db_username or not self.db_passwd or not self.db_dbname:
            frameinfo = getframeinfo(currentframe());
            self.logger.debug("[ MASTER DAEMON "+frameinfo.filaname+":"+str(frameinfo.lineno)+" ]: initialization error: wrong or missing SQL configuration.");
            sys.exit(1);
        ## Function array with option ID
        self.functions_transform = {
              0: utils.convert_none,
              1: utils.convert_temperature,
              2: utils.convert_hundred,
              3: utils.convert_float32
        };

    ## Updates the table containing the hosts.
    # Inserts each host in "hostlist".
    #
    # @param hostlist The list of all the hosts connected on the local network.
    # @param db The database handler used for queries.
    # @return None
    def insert_hostlist_in_db(self, hostlist, db):
        for host in hostlist:
            db.update_datas_in_table('ip_monitor',
                                     {"mac_addr": host._MacAddr},
                                     {"last_update": str(time.time()).split('.')[0],
                                      "ip_addr": host._IpAddr,
                                      "hostname": host._Hostname.split('.')[0]});
        db.personnal_query("DELETE FROM ip_monitor WHERE last_update<"+str(time.time()-7200).split('.')[0]);
        db.updatedb();
        query = ''.join(["UPDATE room_device JOIN ip_monitor ON plus4=mac_addr SET addr=ip_addr WHERE protocol_id=6 AND plus4 != '' AND ip_addr != addr"]);
        self.mysql_handler_personnal_query(query, db);
        db.updatedb();

    ## Updates the enocean log table.
    #
    # @param json_obj JSON object containing the values to update.
    # @param db The database handler used for queries.
    #
    # @return The daemon_id.
    def update_enocean_log(self, json_obj, db):
        daemon_id = 0;
        daemons = db.get_datas_from_table_with_names('daemon', ['daemon_id', 'name', 'serial', 'secretkey']);
        for d in daemons:
            if json_obj['sender_name'] == d[2]:
                daemon_id = d[0];
                break;
        db.insert_datas_in_table('enocean_log',
                                 ['type', 'addr_src', 'addr_dest', 'eo_value', 't_date', 'daemon_id'],
                                 (json_obj['type'], json_obj['src_addr'], json_obj['dst_addr'],
                                  json_obj['value'], json_obj['date'], daemon_id));
        db.updatedb();
        return daemon_id;

    ## Updates the KNX log table.
    #
    # @param json_obj JSON object containing the values to update.
    # @param db The database handler used for queries.
    #
    # @return The daemon_id.
    def update_knx_log(self, json_obj, db):
        daemon_id = 0;
        daemons = db.get_datas_from_table_with_names('daemon', ['daemon_id', 'name', 'serial', 'secretkey']);
        for d in daemons:
            if json_obj['sender_name'] == d[2]:
                daemon_id = d[0];
                break;
        db.insert_datas_in_table('knx_log', ["type", "addr_src", "addr_dest", "knx_value", "t_date", "daemon_id"],
                                 (json_obj['type'], json_obj['src_addr'], json_obj['dst_addr'], json_obj['value'],
                                  json_obj['date'],
                                  daemon_id));
        db.updatedb();
        return daemon_id;

    ## Updates the table room_device_option with long KNX values.
    #
    # @param json_obj JSON object containing some data such as values, addr...
    # @param daemon_id The ID of the slave daemon to who send the packet.
    # @param db The database handler used for queries.
    #
    # @return The result of the query.
    def update_room_device_option_write_long(self, json_obj, daemon_id, db):
        query  = ''.join(["SELECT room_device_option.option_id, room_device.room_device_id, function_answer, dpt_optiondef.dpt_id FROM room_device_option JOIN room_device ON room_device_option.room_device_id=room_device.room_device_id JOIN dpt_optiondef ON dpt_optiondef.option_id=room_device_option.option_id AND dpt_optiondef.protocol_id=room_device.protocol_id AND dpt_optiondef.dpt_id=room_device_option.dpt_id WHERE daemon_id=",
                  str(daemon_id), " AND room_device_option.addr=\"", str(json_obj['dst_addr']), "\""]);
        res = self.mysql_handler_personnal_query(query, db);
        if not res:
            query = ''.join(["SELECT room_device_option.option_id, room_device.room_device_id, function_answer, dpt_optiondef.dpt_id FROM room_device_option JOIN room_device ON room_device_option.room_device_id=room_device.room_device_id JOIN dpt_optiondef ON dpt_optiondef.option_id=room_device_option.option_id AND dpt_optiondef.protocol_id=room_device.protocol_id AND dpt_optiondef.dpt_id=room_device_option.dpt_id WHERE ",
                     str(daemon_id), " AND room_device_option.addr_plus=\"", str(json_obj['dst_addr']), "\""]);
            res = self.mysql_handler_personnal_query(query, db);
        for r in res:
            if int(r[0]) == 13:
                if not json_obj['value']:
                    up = 'UPDATE room_device_option SET opt_value=0 WHERE room_device_id=' + str(r[1]) + ' AND option_id=12';
                else:
                    up = 'UPDATE room_device_option SET opt_value=1 WHERE room_device_id=' + str(r[1]) + ' AND option_id=12';
                self.logger.debug('update_room_device_option write_long: up = ' + up);
                self.mysql_handler_personnal_query(up, db);
                query = ''.join(["UPDATE room_device_option SET opt_value=\"", str(json_obj['value']),
                         "\" WHERE room_device_id=", str(r[1]), " AND option_id=", str(r[0])]);
                self.mysql_handler_personnal_query(query, db);
            elif int(r[0]) == 72 or int(r[0]) == 388:
                val = int(json_obj['value']);
                res = utils.convert_temperature(val);
                query = ''.join(["UPDATE room_device_option JOIN room_device ON room_device_option.room_device_id=room_device.room_device_id SET opt_value=\"",
                         str(res), "\" WHERE daemon_id=", str(daemon_id),
                         " AND room_device_option.addr=\"", str(json_obj['dst_addr']), "\"",
                         " OR ", "room_device_option.addr_plus=\"", str(json_obj['dst_addr']), "\""]);
                self.logger.debug('update_room_device_option write_long: query = ' + query);
                self.mysql_handler_personnal_query(query, db);
            else:
                val = self.functions_transform[r[2]](int(json_obj['value']));
                up = ''.join(["UPDATE room_device_option SET opt_value=\"", str(val),
                     "\" WHERE room_device_id=", str(r[1]), " AND option_id=\"", str(r[0]), "\""]);
                self.logger.debug('update_room_device_option write_long: up = ' + up)
                self.mysql_handler_personnal_query(up, db);
        return res

    ## Updates the table room_device_option with resp KNX values.
    #
    # @param json_obj JSON object containing data to update.
    # @param daemon_id The ID of the slave daemon to who send the packet.
    # @param db The database handler used for queries.
    #
    # @return The result of the query.
    def update_room_device_option_resp(self, json_obj, daemon_id, db):
        query = ''.join(["SELECT option_id, room_device.room_device_id, function_answer FROM ",
              "room_device_option JOIN room_device ON ",
              "room_device_option.room_device_id=room_device.room_device_id ",
              "JOIN dpt_optiondef ON dpt_optiondef.option_id=room_device_option.option_id AND ",
              "dpt_optiondef.protocol_id=room_device.protocol_id AND dpt_optiondef.dpt_id=room_device_option.dpt_id ",
              "WHERE daemon_id=", str(daemon_id), " AND room_device_option.addr=\"", str(json_obj['dst_addr']), "\""]);
        res = self.mysql_handler_personnal_query(query, db);
        for r in res:
            val = self.functions_transform[r[2]](int(json_obj['value']));
            query = ''.join(["UPDATE room_device_option JOIN room_device ON ",
                  "room_device_option.room_device_id=room_device.room_device_id SET ",
                  "opt_value=\"", str(val), "\" WHERE daemon_id=", str(daemon_id),
                  " AND room_device_option.addr=\"", str(json_obj['dst_addr']), "\""]);
            self.logger.debug("update_room_device_option resp query = "+query);
            self.mysql_handler_personnal_query(query, db);
        return res

    ## Updates the table room_device_option with short KNX values.
    #
    # @param json_obj JSON object containing data to update.
    # @param daemon_id The ID of the daemon to who send the packet.
    # @param db The database handler used for queries.
    #
    # @return The result of the query.
    def update_room_device_option_write_short(self, json_obj, daemon_id, db):
        query = ''.join(["SELECT option_id, room_device.room_device_id FROM ",
              "room_device_option JOIN room_device ON ",
              "room_device_option.room_device_id=room_device.room_device_id WHERE ",
              "daemon_id=", str(daemon_id), " AND room_device_option.addr=\"",
              str(json_obj['dst_addr']), "\""]);
        #self.logger.debug("update_room_device_option write_short query : " + query);
        res = self.mysql_handler_personnal_query(query, db);
        if not res:
            query = ''.join(["SELECT option_id, room_device.room_device_id FROM ",
                  "room_device_option JOIN room_device ON ",
                  "room_device_option.room_device_id=room_device.room_device_id WHERE ",
                  "daemon_id=", str(daemon_id), " AND room_device_option.addr_plus=\"",
                  str(json_obj['dst_addr']), "\""]);
            #self.logger.debug("update_room_device_option write_short query : " + query);
            res = self.mysql_handler_personnal_query(query, db);
        for r in res:
            if (int(r[0]) == MasterDaemon.OPTION_ON_OFF or int(r[0]) == MasterDaemon.OPTION_UP_DOWN or int(r[0]) == MasterDaemon.OPTION_OPEN_CLOSE):
                up = 'UPDATE room_device_option SET opt_value=';
                if not json_obj['value']:
                    up += '0';
                else:
                    up += '255';
                up += ' WHERE room_device_id='+str(r[1])+" AND option_id=13";
                self.logger.debug("update_room_device_option write_short up1: "+up);
                self.mysql_handler_personnal_query(up, db);
            up = ''.join(["UPDATE room_device_option SET opt_value=", str(json_obj['value']),
                  " WHERE room_device_id=", str(r[1]), " AND option_id=", str(r[0]), ""]);
            self.logger.debug("update_room_device_option write_short up2: "+up);
            self.mysql_handler_personnal_query(up, db);
        return res

    ## Sends a personnal query to the database.
    #
    # @param query The query to send to the database.
    # @param db The database handler used for queries.
    # @return Result of the query.
    def mysql_handler_personnal_query(self, query, db=0):
        tmp = db;
        if not tmp:
            db = MysqlHandler(self.db_username, self.db_passwd, self.db_dbname);
        res = db.personnal_query(query);
        db.updatedb();
        if not tmp:
            db.close();
        return res;

    ## Retrieves each daemon stored in the database.
    #
    # @param db The database handler used for queries.
    #
    # @return An array with all the daemons found.
    def get_daemons(self, db):
        daemons = db.get_datas_from_table_with_names('daemon', ['daemon_id', 'name', 'serial', 'secretkey']);
        return daemons;
Example #19
0
class MasterSql:

    ## The constructor
    #
    # @param log_flag Flag saying if the logs are active or not.
    def __init__(self, log_flag=False):
        ## Logger object for formatting and printing logs
        self.logger = Logger(log_flag, LOG_FILE)
        self._parser = DaemonConfigParser(MASTER_CONF_FILE)
        ## Username for the database, searched in configuration file
        self.db_username = self._parser.getValueFromSection(
            MASTER_CONF_MYSQL_SECTION, MASTER_CONF_MYSQL_USER_ENTRY)
        ## Password for the database, searched in configuration file
        self.db_passwd = self._parser.getValueFromSection(
            MASTER_CONF_MYSQL_SECTION, MASTER_CONF_MYSQL_PASSWORD_ENTRY)
        ## Database name for the database, searched in configuration file
        self.db_dbname = self._parser.getValueFromSection(
            MASTER_CONF_MYSQL_SECTION, MASTER_CONF_MYSQL_DB_NAME_ENTRY)
        if not self.db_username or not self.db_passwd or not self.db_dbname:
            frameinfo = getframeinfo(currentframe())
            self.logger.debug(
                "[ MASTER DAEMON " + frameinfo.filaname + ":" +
                str(frameinfo.lineno) +
                " ]: initialization error: wrong or missing SQL configuration."
            )
            sys.exit(1)
        ## Function array with option ID
        self.functions_transform = {
            0: utils.convert_none,
            1: utils.convert_temperature,
            2: utils.convert_hundred,
            3: utils.convert_float32
        }

    ## Updates the table containing the hosts.
    # Inserts each host in "hostlist".
    #
    # @param hostlist The list of all the hosts connected on the local network.
    # @param db The database handler used for queries.
    # @return None
    def insert_hostlist_in_db(self, hostlist, db):
        for host in hostlist:
            db.update_datas_in_table(
                'ip_monitor', {"mac_addr": host._MacAddr}, {
                    "last_update": str(time.time()).split('.')[0],
                    "ip_addr": host._IpAddr,
                    "hostname": host._Hostname.split('.')[0]
                })
        db.personnal_query("DELETE FROM ip_monitor WHERE last_update<" +
                           str(time.time() - 7200).split('.')[0])
        db.updatedb()
        query = ''.join([
            "UPDATE room_device JOIN ip_monitor ON plus4=mac_addr SET addr=ip_addr WHERE protocol_id=6 AND plus4 != '' AND ip_addr != addr"
        ])
        self.mysql_handler_personnal_query(query, db)
        db.updatedb()

    ## Updates the enocean log table.
    #
    # @param json_obj JSON object containing the values to update.
    # @param db The database handler used for queries.
    #
    # @return The daemon_id.
    def update_enocean_log(self, json_obj, db):
        daemon_id = 0
        daemons = db.get_datas_from_table_with_names(
            'daemon', ['daemon_id', 'name', 'serial', 'secretkey'])
        for d in daemons:
            if json_obj['sender_name'] == d[2]:
                daemon_id = d[0]
                break
        db.insert_datas_in_table('enocean_log', [
            'type', 'addr_src', 'addr_dest', 'eo_value', 't_date', 'daemon_id'
        ], (json_obj['type'], json_obj['src_addr'], json_obj['dst_addr'],
            json_obj['value'], json_obj['date'], daemon_id))
        db.updatedb()
        return daemon_id

    ## Updates the KNX log table.
    #
    # @param json_obj JSON object containing the values to update.
    # @param db The database handler used for queries.
    #
    # @return The daemon_id.
    def update_knx_log(self, json_obj, db):
        daemon_id = 0
        daemons = db.get_datas_from_table_with_names(
            'daemon', ['daemon_id', 'name', 'serial', 'secretkey'])
        for d in daemons:
            if json_obj['sender_name'] == d[2]:
                daemon_id = d[0]
                break
        db.insert_datas_in_table('knx_log', [
            "type", "addr_src", "addr_dest", "knx_value", "t_date", "daemon_id"
        ], (json_obj['type'], json_obj['src_addr'], json_obj['dst_addr'],
            json_obj['value'], json_obj['date'], daemon_id))
        db.updatedb()
        return daemon_id

    ## Updates the table room_device_option with long KNX values.
    #
    # @param json_obj JSON object containing some data such as values, addr...
    # @param daemon_id The ID of the slave daemon to who send the packet.
    # @param db The database handler used for queries.
    #
    # @return The result of the query.
    def update_room_device_option_write_long(self, json_obj, daemon_id, db):
        query = ''.join([
            "SELECT room_device_option.option_id, room_device.room_device_id, function_answer, dpt_optiondef.dpt_id FROM room_device_option JOIN room_device ON room_device_option.room_device_id=room_device.room_device_id JOIN dpt_optiondef ON dpt_optiondef.option_id=room_device_option.option_id AND dpt_optiondef.protocol_id=room_device.protocol_id AND dpt_optiondef.dpt_id=room_device_option.dpt_id WHERE daemon_id=",
            str(daemon_id), " AND room_device_option.addr=\"",
            str(json_obj['dst_addr']), "\""
        ])
        res = self.mysql_handler_personnal_query(query, db)
        if not res:
            query = ''.join([
                "SELECT room_device_option.option_id, room_device.room_device_id, function_answer, dpt_optiondef.dpt_id FROM room_device_option JOIN room_device ON room_device_option.room_device_id=room_device.room_device_id JOIN dpt_optiondef ON dpt_optiondef.option_id=room_device_option.option_id AND dpt_optiondef.protocol_id=room_device.protocol_id AND dpt_optiondef.dpt_id=room_device_option.dpt_id WHERE ",
                str(daemon_id), " AND room_device_option.addr_plus=\"",
                str(json_obj['dst_addr']), "\""
            ])
            res = self.mysql_handler_personnal_query(query, db)
        for r in res:
            if int(r[0]) == 13:
                if not json_obj['value']:
                    up = 'UPDATE room_device_option SET opt_value=0 WHERE room_device_id=' + str(
                        r[1]) + ' AND option_id=12'
                else:
                    up = 'UPDATE room_device_option SET opt_value=1 WHERE room_device_id=' + str(
                        r[1]) + ' AND option_id=12'
                self.logger.debug(
                    'update_room_device_option write_long: up = ' + up)
                self.mysql_handler_personnal_query(up, db)
                query = ''.join([
                    "UPDATE room_device_option SET opt_value=\"",
                    str(json_obj['value']), "\" WHERE room_device_id=",
                    str(r[1]), " AND option_id=",
                    str(r[0])
                ])
                self.mysql_handler_personnal_query(query, db)
            elif int(r[0]) == 72 or int(r[0]) == 388:
                val = int(json_obj['value'])
                res = utils.convert_temperature(val)
                query = ''.join([
                    "UPDATE room_device_option JOIN room_device ON room_device_option.room_device_id=room_device.room_device_id SET opt_value=\"",
                    str(res), "\" WHERE daemon_id=",
                    str(daemon_id), " AND room_device_option.addr=\"",
                    str(json_obj['dst_addr']), "\"", " OR ",
                    "room_device_option.addr_plus=\"",
                    str(json_obj['dst_addr']), "\""
                ])
                self.logger.debug(
                    'update_room_device_option write_long: query = ' + query)
                self.mysql_handler_personnal_query(query, db)
            else:
                val = self.functions_transform[r[2]](int(json_obj['value']))
                up = ''.join([
                    "UPDATE room_device_option SET opt_value=\"",
                    str(val), "\" WHERE room_device_id=",
                    str(r[1]), " AND option_id=\"",
                    str(r[0]), "\""
                ])
                self.logger.debug(
                    'update_room_device_option write_long: up = ' + up)
                self.mysql_handler_personnal_query(up, db)
        return res

    ## Updates the table room_device_option with resp KNX values.
    #
    # @param json_obj JSON object containing data to update.
    # @param daemon_id The ID of the slave daemon to who send the packet.
    # @param db The database handler used for queries.
    #
    # @return The result of the query.
    def update_room_device_option_resp(self, json_obj, daemon_id, db):
        query = ''.join([
            "SELECT option_id, room_device.room_device_id, function_answer FROM ",
            "room_device_option JOIN room_device ON ",
            "room_device_option.room_device_id=room_device.room_device_id ",
            "JOIN dpt_optiondef ON dpt_optiondef.option_id=room_device_option.option_id AND ",
            "dpt_optiondef.protocol_id=room_device.protocol_id AND dpt_optiondef.dpt_id=room_device_option.dpt_id ",
            "WHERE daemon_id=",
            str(daemon_id), " AND room_device_option.addr=\"",
            str(json_obj['dst_addr']), "\""
        ])
        res = self.mysql_handler_personnal_query(query, db)
        for r in res:
            val = self.functions_transform[r[2]](int(json_obj['value']))
            query = ''.join([
                "UPDATE room_device_option JOIN room_device ON ",
                "room_device_option.room_device_id=room_device.room_device_id SET ",
                "opt_value=\"",
                str(val), "\" WHERE daemon_id=",
                str(daemon_id), " AND room_device_option.addr=\"",
                str(json_obj['dst_addr']), "\""
            ])
            self.logger.debug("update_room_device_option resp query = " +
                              query)
            self.mysql_handler_personnal_query(query, db)
        return res

    ## Updates the table room_device_option with short KNX values.
    #
    # @param json_obj JSON object containing data to update.
    # @param daemon_id The ID of the daemon to who send the packet.
    # @param db The database handler used for queries.
    #
    # @return The result of the query.
    def update_room_device_option_write_short(self, json_obj, daemon_id, db):
        query = ''.join([
            "SELECT option_id, room_device.room_device_id FROM ",
            "room_device_option JOIN room_device ON ",
            "room_device_option.room_device_id=room_device.room_device_id WHERE ",
            "daemon_id=",
            str(daemon_id), " AND room_device_option.addr=\"",
            str(json_obj['dst_addr']), "\""
        ])
        #self.logger.debug("update_room_device_option write_short query : " + query);
        res = self.mysql_handler_personnal_query(query, db)
        if not res:
            query = ''.join([
                "SELECT option_id, room_device.room_device_id FROM ",
                "room_device_option JOIN room_device ON ",
                "room_device_option.room_device_id=room_device.room_device_id WHERE ",
                "daemon_id=",
                str(daemon_id), " AND room_device_option.addr_plus=\"",
                str(json_obj['dst_addr']), "\""
            ])
            #self.logger.debug("update_room_device_option write_short query : " + query);
            res = self.mysql_handler_personnal_query(query, db)
        for r in res:
            if (int(r[0]) == MasterDaemon.OPTION_ON_OFF
                    or int(r[0]) == MasterDaemon.OPTION_UP_DOWN
                    or int(r[0]) == MasterDaemon.OPTION_OPEN_CLOSE):
                up = 'UPDATE room_device_option SET opt_value='
                if not json_obj['value']:
                    up += '0'
                else:
                    up += '255'
                up += ' WHERE room_device_id=' + str(
                    r[1]) + " AND option_id=13"
                self.logger.debug(
                    "update_room_device_option write_short up1: " + up)
                self.mysql_handler_personnal_query(up, db)
            up = ''.join([
                "UPDATE room_device_option SET opt_value=",
                str(json_obj['value']), " WHERE room_device_id=",
                str(r[1]), " AND option_id=",
                str(r[0]), ""
            ])
            self.logger.debug("update_room_device_option write_short up2: " +
                              up)
            self.mysql_handler_personnal_query(up, db)
        return res

    ## Sends a personnal query to the database.
    #
    # @param query The query to send to the database.
    # @param db The database handler used for queries.
    # @return Result of the query.
    def mysql_handler_personnal_query(self, query, db=0):
        tmp = db
        if not tmp:
            db = MysqlHandler(self.db_username, self.db_passwd, self.db_dbname)
        res = db.personnal_query(query)
        db.updatedb()
        if not tmp:
            db.close()
        return res

    ## Retrieves each daemon stored in the database.
    #
    # @param db The database handler used for queries.
    #
    # @return An array with all the daemons found.
    def get_daemons(self, db):
        daemons = db.get_datas_from_table_with_names(
            'daemon', ['daemon_id', 'name', 'serial', 'secretkey'])
        return daemons
Example #20
0
def slave_conf_copy():
    file_from = DaemonConfigParser(SLAVE_CONF_FILE_FROM)
    file_to = DaemonConfigParser(SLAVE_CONF_FILE_TO)

    #listen
    var = file_from.getValueFromSection('listen', 'port')
    file_to.writeValueFromSection('listen', 'port', var)

    #connect
    var = file_from.getValueFromSection('connect', 'port')
    file_to.writeValueFromSection('connect', 'port', var)

    #knx
    var = file_from.getValueFromSection('knx', 'port')
    file_to.writeValueFromSection('knx', 'port', var)
    var = file_from.getValueFromSection('knx', 'interface')
    file_to.writeValueFromSection('knx', 'interface', var)
    var = file_from.getValueFromSection('knx', 'activated')
    file_to.writeValueFromSection('knx', 'activated', var)

    #enocean
    var = file_from.getValueFromSection('enocean', 'port')
    file_to.writeValueFromSection('enocean', 'port', var)
    var = file_from.getValueFromSection('enocean', 'interface')
    file_to.writeValueFromSection('enocean', 'interface', var)

    #cron
    var = file_from.getValueFromSection('cron', 'port')
    file_to.writeValueFromSection('cron', 'port', var)
    var = file_from.getValueFromSection('cron', 'address')
    file_to.writeValueFromSection('cron', 'address', var)

    #personnal_key
    var = file_from.getValueFromSection('personnal_key', 'aes')
    file_to.writeValueFromSection('personnal_key', 'aes', var)

    #openvpn
    var = file_from.getValueFromSection('openvpn', 'openvpnserver')
    file_to.writeValueFromSection('openvpn', 'openvpnserver', var)
Example #21
0
# Master daemon for D3 boxes.
#
# Developed by GreenLeaf.

import sys
sys.path.append('/usr/lib/domoleaf')
import socket
import json
from DaemonConfigParser import *

## Script sending the command to open or close the UPnP to the master.
if __name__ == "__main__":
    try:
        parser = DaemonConfigParser('/etc/domoleaf/master.conf')
        ip = '127.0.0.1'
        port = parser.getValueFromSection('listen', 'port_cmd')
        s = socket.create_connection((ip, port))
        obj = {
            "packet_type":
            "cron_upnp",
            "data": [{
                "action": "open",
                "configuration_id": 1,
                "protocol": "TCP"
            }, {
                "action": "close",
                "configuration_id": 2,
                "protocol": "TCP"
            }]
        }
        obj_str = json.JSONEncoder().encode(obj)
Example #22
0
class MasterSql:
    """
    Master daemon SQL management class.
    """
    def __init__(self, log_flag = True):
        self.logger = Logger(log_flag, '/var/log/domoleaf/domomaster.log');
        self._parser = DaemonConfigParser(MASTER_CONF_FILE);
        self.db_username = self._parser.getValueFromSection(MASTER_CONF_MYSQL_SECTION,
                                                            MASTER_CONF_MYSQL_USER_ENTRY);
        self.db_passwd = self._parser.getValueFromSection(MASTER_CONF_MYSQL_SECTION,
                                                          MASTER_CONF_MYSQL_PASSWORD_ENTRY);
        self.db_dbname = self._parser.getValueFromSection(MASTER_CONF_MYSQL_SECTION,
                                                          MASTER_CONF_MYSQL_DB_NAME_ENTRY);
        if not self.db_username or not self.db_passwd or not self.db_dbname:
            frameinfo = getframeinfo(currentframe());
            self.logger.debug("[ MASTER DAEMON "+frameinfo.filaname+":"+str(frameinfo.lineno)+" ]: initialization error: wrong or missing SQL configuration.");
            sys.exit(1);
        self.functions_transform = {
              0: utils.convert_none,
              1: utils.convert_temperature,
              2: utils.convert_hundred,
              3: utils.convert_float32
        };

    def insert_hostlist_in_db(self, hostlist, db):
        """
        Update of the table containing the hosts. Inserts each host in 'hostlist'.
        """
        for host in hostlist:
            db.update_datas_in_table('ip_monitor',
                                     {"mac_addr": host._MacAddr},
                                     {"last_update": str(time.time()).split('.')[0],
                                      "ip_addr": host._IpAddr,
                                      "hostname": host._Hostname.split('.')[0]});
        db.personnal_query("DELETE FROM ip_monitor WHERE last_update<"+str(time.time()-7200).split('.')[0]);
        db.updatedb();
        query = ''.join(["UPDATE room_device JOIN ip_monitor ON plus4=mac_addr SET addr=ip_addr WHERE protocol_id=6 AND plus4 != '' AND ip_addr != addr"]);
        self.mysql_handler_personnal_query(query, db);
        db.updatedb();

    def update_enocean_log(self, json_obj, db):
        """
        Update of the enocean log table with values from 'json_obj'
        """
        daemon_id = 0;
        daemons = db.get_datas_from_table_with_names('daemon', ['daemon_id', 'name', 'serial', 'secretkey']);
        for d in daemons:
            if json_obj['sender_name'] == d[2]:
                daemon_id = d[0];
                break;
        db.insert_datas_in_table('enocean_log',
                                 ['type', 'addr_src', 'addr_dest', 'eo_value', 't_date', 'daemon_id'],
                                 (json_obj['type'], json_obj['src_addr'], json_obj['dst_addr'],
                                  json_obj['value'], json_obj['date'], daemon_id));
        db.updatedb();
        return daemon_id;

    def update_knx_log(self, json_obj, db):
        """
        Update of the knx log table with values from 'json_obj'
        """
        daemon_id = 0;
        daemons = db.get_datas_from_table_with_names('daemon', ['daemon_id', 'name', 'serial', 'secretkey']);
        for d in daemons:
            if json_obj['sender_name'] == d[2]:
                daemon_id = d[0];
                break;
        db.insert_datas_in_table('knx_log', ["type", "addr_src", "addr_dest", "knx_value", "t_date", "daemon_id"],
                                 (json_obj['type'], json_obj['src_addr'], json_obj['dst_addr'], json_obj['value'],
                                  json_obj['date'],
                                  daemon_id));
        db.updatedb();
        return daemon_id;

    def update_room_device_option_write_long(self, json_obj, daemon_id, db):
        """
        Update of the table room_device_option with long KNX value
        """
        query  = ''.join(["SELECT room_device_option.option_id, room_device.room_device_id, function_answer, dpt_optiondef.dpt_id FROM room_device_option JOIN room_device ON room_device_option.room_device_id=room_device.room_device_id JOIN dpt_optiondef ON dpt_optiondef.option_id=room_device_option.option_id AND dpt_optiondef.protocol_id=room_device.protocol_id AND dpt_optiondef.dpt_id=room_device_option.dpt_id WHERE daemon_id=",
                  str(daemon_id), " AND room_device_option.addr=\"", str(json_obj['dst_addr']), "\""]);
        res = self.mysql_handler_personnal_query(query, db);
        if not res:
            query = ''.join(["SELECT room_device_option.option_id, room_device.room_device_id, function_answer, dpt_optiondef.dpt_id FROM room_device_option JOIN room_device ON room_device_option.room_device_id=room_device.room_device_id JOIN dpt_optiondef ON dpt_optiondef.option_id=room_device_option.option_id AND dpt_optiondef.protocol_id=room_device.protocol_id AND dpt_optiondef.dpt_id=room_device_option.dpt_id WHERE ",
                     str(daemon_id), " AND room_device_option.addr_plus=\"", str(json_obj['dst_addr']), "\""]);
            res = self.mysql_handler_personnal_query(query, db);
        for r in res:
            if int(r[0]) == 13:
                if not json_obj['value']:
                    up = 'UPDATE room_device_option SET opt_value=0 WHERE room_device_id=' + str(r[1]) + ' AND option_id=12';
                else:
                    up = 'UPDATE room_device_option SET opt_value=1 WHERE room_device_id=' + str(r[1]) + ' AND option_id=12';
                self.logger.debug('update_room_device_option write_long: up = ' + up);
                self.mysql_handler_personnal_query(up, db);
                query = ''.join(["UPDATE room_device_option SET opt_value=\"", str(json_obj['value']),
                         "\" WHERE room_device_id=", str(r[1]), " AND option_id=", str(r[0])]);
                self.mysql_handler_personnal_query(query, db);
            elif int(r[0]) == 72 or int(r[0]) == 388:
                val = int(json_obj['value']);
                res = utils.convert_temperature(val);
                query = ''.join(["UPDATE room_device_option JOIN room_device ON room_device_option.room_device_id=room_device.room_device_id SET opt_value=\"",
                         str(res), "\" WHERE daemon_id=", str(daemon_id),
                         " AND room_device_option.addr=\"", str(json_obj['dst_addr']), "\"", 
                         " OR ", "room_device_option.addr_plus=\"", str(json_obj['dst_addr']), "\""]);
                self.logger.debug('update_room_device_option write_long: query = ' + query);
                self.mysql_handler_personnal_query(query, db);
            else:
                val = self.functions_transform[r[2]](int(json_obj['value']));
                up = ''.join(["UPDATE room_device_option SET opt_value=\"", str(val),
                     "\" WHERE room_device_id=", str(r[1]), " AND option_id=\"", str(r[0]), "\""]);
                self.logger.debug('update_room_device_option write_long: up = ' + up)
                self.mysql_handler_personnal_query(up, db);
        return res

    def update_room_device_option_resp(self, json_obj, daemon_id, db):
        """
        Update of the table room_device_option with resp KNX value
        """
        query = ''.join(["SELECT option_id, room_device.room_device_id, function_answer FROM ",
              "room_device_option JOIN room_device ON ",
              "room_device_option.room_device_id=room_device.room_device_id ",
              "JOIN dpt_optiondef ON dpt_optiondef.option_id=room_device_option.option_id AND ",
              "dpt_optiondef.protocol_id=room_device.protocol_id AND dpt_optiondef.dpt_id=room_device_option.dpt_id ",
              "WHERE daemon_id=", str(daemon_id), " AND room_device_option.addr=\"", str(json_obj['dst_addr']), "\""]);
        res = self.mysql_handler_personnal_query(query, db);
        for r in res:
            val = self.functions_transform[r[2]](int(json_obj['value']));
            query = ''.join(["UPDATE room_device_option JOIN room_device ON ",
                  "room_device_option.room_device_id=room_device.room_device_id SET ",
                  "opt_value=\"", str(val), "\" WHERE daemon_id=", str(daemon_id),
                  " AND room_device_option.addr=\"", str(json_obj['dst_addr']), "\""]);
            self.logger.debug("update_room_device_option resp query = "+query);
            self.mysql_handler_personnal_query(query, db);
        return res

    def update_room_device_option_write_short(self, json_obj, daemon_id, db):
        """
        Update of the table room_device_option with short KNX value
        """
        query = ''.join(["SELECT option_id, room_device.room_device_id FROM ",
              "room_device_option JOIN room_device ON ",
              "room_device_option.room_device_id=room_device.room_device_id WHERE ",
              "daemon_id=", str(daemon_id), " AND room_device_option.addr=\"",
              str(json_obj['dst_addr']), "\""]);
        #self.logger.debug("update_room_device_option write_short query : " + query);
        res = self.mysql_handler_personnal_query(query, db);
        if not res:
            query = ''.join(["SELECT option_id, room_device.room_device_id FROM ",
                  "room_device_option JOIN room_device ON ",
                  "room_device_option.room_device_id=room_device.room_device_id WHERE ",
                  "daemon_id=", str(daemon_id), " AND room_device_option.addr_plus=\"",
                  str(json_obj['dst_addr']), "\""]);
            #self.logger.debug("update_room_device_option write_short query : " + query);
            res = self.mysql_handler_personnal_query(query, db);
        for r in res:
            if (int(r[0]) == MasterDaemon.OPTION_ON_OFF or int(r[0]) == MasterDaemon.OPTION_UP_DOWN or int(r[0]) == MasterDaemon.OPTION_OPEN_CLOSE):
                up = 'UPDATE room_device_option SET opt_value=';
                if not json_obj['value']:
                    up += '0';
                else:
                    up += '255';
                up += ' WHERE room_device_id='+str(r[1])+" AND option_id=13";
                self.logger.debug("update_room_device_option write_short up1: "+up);
                self.mysql_handler_personnal_query(up, db);
            up = ''.join(["UPDATE room_device_option SET opt_value=", str(json_obj['value']),
                  " WHERE room_device_id=", str(r[1]), " AND option_id=", str(r[0]), ""]);
            self.logger.debug("update_room_device_option write_short up2: "+up);
            self.mysql_handler_personnal_query(up, db);
        return res

    def mysql_handler_personnal_query(self, query, db=0):
        """
        Sends personnal query to the database and returns the result
        """
        tmp = db;
        if not tmp:
            db = MysqlHandler(self.db_username, self.db_passwd, self.db_dbname);
        res = db.personnal_query(query);
        db.updatedb();
        if not tmp:
            db.close();
        return res;

    def get_daemons(self, db):
        """
        Retrieves each daemon stored in the database
        """
        daemons = db.get_datas_from_table_with_names('daemon', ['daemon_id', 'name', 'serial', 'secretkey']);
        return daemons;
Example #23
0
class MasterSql:
    """
    Master daemon SQL management class.
    """

    def __init__(self, log_flag=False):
        self.logger = Logger(log_flag, "/var/log/glmaser.log")
        self._parser = DaemonConfigParser(MASTER_CONF_FILE)
        self.db_username = self._parser.getValueFromSection(MASTER_CONF_MYSQL_SECTION, MASTER_CONF_MYSQL_USER_ENTRY)
        self.db_passwd = self._parser.getValueFromSection(MASTER_CONF_MYSQL_SECTION, MASTER_CONF_MYSQL_PASSWORD_ENTRY)
        self.db_dbname = self._parser.getValueFromSection(MASTER_CONF_MYSQL_SECTION, MASTER_CONF_MYSQL_DB_NAME_ENTRY)
        if not self.db_username or not self.db_passwd or not self.db_dbname:
            frameinfo = getframeinfo(currentframe())
            self.logger.info(
                "[ MASTER DAEMON "
                + frameinfo.filaname
                + ":"
                + str(frameinfo.lineno)
                + " ]: initialization error: wrong or missing SQL configuration."
            )
            sys.exit(1)

    def insert_hostlist_in_db(self, hostlist):
        """
        Update of the table containing the hosts. Inserts each host in 'hostlist'.
        """
        db = MysqlHandler(self.db_username, self.db_passwd, self.db_dbname)
        for host in hostlist:
            db.update_datas_in_table(
                "ip_monitor",
                {"mac_addr": host._MacAddr},
                {
                    "last_update": str(time.time()).split(".")[0],
                    "ip_addr": host._IpAddr,
                    "hostname": host._Hostname.split(".")[0],
                },
            )
        db.updatedb()
        db.close()

    def update_enocean_log(self, json_obj):
        """
        Update of the enocean log table with values from 'json_obj'
        """
        daemon_id = 0
        db = MysqlHandler(self.db_username, self.db_passwd, self.db_dbname)
        daemons = db.get_datas_from_table_with_names("daemon", ["daemon_id", "name", "serial", "secretkey"])
        for d in daemons:
            if json_obj["sender_name"] == d[2]:
                daemon_id = d[0]
                break
        db.insert_datas_in_table(
            "enocean_log",
            ["type", "addr_src", "addr_dest", "eo_value", "t_date", "daemon_id"],
            (
                json_obj["type"],
                json_obj["src_addr"],
                json_obj["dst_addr"],
                json_obj["value"],
                json_obj["date"],
                daemon_id,
            ),
        )
        db.updatedb()
        db.close()
        return daemon_id

    def update_knx_log(self, json_obj):
        """
        Update of the knx log table with values from 'json_obj'
        """
        daemon_id = 0
        db = MysqlHandler(self.db_username, self.db_passwd, self.db_dbname)
        daemons = db.get_datas_from_table_with_names("daemon", ["daemon_id", "name", "serial", "secretkey"])
        for d in daemons:
            if json_obj["sender_name"] == d[2]:
                daemon_id = d[0]
                break
        db.insert_datas_in_table(
            "knx_log",
            ["type", "addr_src", "addr_dest", "knx_value", "t_date", "daemon_id"],
            (
                json_obj["type"],
                json_obj["src_addr"],
                json_obj["dst_addr"],
                json_obj["value"],
                json_obj["date"],
                daemon_id,
            ),
        )
        db.updatedb()
        db.close()
        return daemon_id

    def update_room_device_option_write_long(self, json_obj, daemon_id):
        """
        Update of the table room_device_option with long KNX value
        """
        query = "SELECT option_id, room_device.room_device_id, addr_plus FROM room_device_option "
        query += "JOIN room_device ON room_device_option.room_device_id=room_device.room_device_id "
        query += "WHERE daemon_id=" + str(daemon_id) + ' AND room_device_option.addr="'
        query += str(json_obj["dst_addr"]) + '"'
        res = self.mysql_handler_personnal_query(query)
        query = "UPDATE room_device_option JOIN room_device ON "
        query += "room_device_option.room_device_id=room_device.room_device_id SET "
        for r in res:
            if int(r[0]) == MasterDaemon.OPTION_VAR:
                up = "UPDATE room_device_option SET valeur="
                if json_obj["value"] == 0:
                    up += "0"
                else:
                    up += "1"
                up += " WHERE room_device_id=" + str(r[1])
                up += " AND option_id=12"
                self.logger.info("update_room_device_option write_long: up = " + up)
                self.mysql_handler_personnal_query(up)
            elif int(r[0]) == MasterDaemon.OPTION_TEMPERATURE or int(r[0]) == MasterDaemon.OPTION_TEMPERATURE_W:
                val = int(json_obj["value"])
                res = utils.convert_temperature(val)
                query += 'valeur="' + str(res) + '" WHERE daemon_id=' + str(daemon_id)
                query += ' AND room_device_option.addr="' + str(json_obj["dst_addr"]) + '"'
                self.logger.info("update_room_device_option write_long: query = " + query)
                self.mysql_handler_personnal_query(query)
            else:
                up = 'UPDATE room_device_option SET valeur="' + str(json_obj["value"])
                up += '" WHERE room_device_id=' + str(r[1]) + ' AND option_id="' + str(r[0]) + '"'
                self.logger.info("update_room_device_option write_long: up = " + up)
                self.mysql_handler_personnal_query(up)

    def update_room_device_option_resp(self, json_obj, daemon_id):
        """
        Update of the table room_device_option with resp KNX value
        """
        query = "SELECT option_id, room_device.room_device_id FROM "
        query += "room_device_option JOIN room_device ON "
        query += "room_device_option.room_device_id=room_device.room_device_id WHERE "
        query += "daemon_id=" + str(daemon_id) + ' AND room_device_option.addr="'
        query += str(json_obj["dst_addr"]) + '"'
        res = self.mysql_handler_personnal_query(query)
        query = "UPDATE room_device_option JOIN room_device ON "
        query += "room_device_option.room_device_id=room_device.room_device_id SET "
        if type(res).__name__ == "list":
            for r in res:
                if int(r[0]) == OPTION_TEMPERATURE:
                    val = int(json_obj["value"])
                    res = utils.convert_temperature(val)
                    query += 'valeur="' + str(res) + '" WHERE daemon_id=' + str(daemon_id)
                    query += ' AND room_device_option.addr="' + str(json_obj["dst_addr"]) + '"'
                    self.logger.info("update_room_device_option resp query = " + query)
                    self.mysql_handler_personnal_query(query)
                else:
                    query += 'valeur="' + str(json_obj["value"]) + '" WHERE daemon_id=' + str(daemon_id)
                    query += ' AND addr_plus="' + str(json_obj["dst_addr"]) + '"'
                    self.logger.info("update_room_device_option resp query = " + query)
                    self.mysql_handler_personnal_query(query)
        else:
            if int(r[0]) == OPTION_TEMPERATURE:
                val = int(json_obj["value"])
                res = utils.convert_temperature(val)
                query += 'valeur="' + str(res) + '" WHERE daemon_id=' + str(daemon_id)
                query += ' AND room_device_option.addr="' + str(json_obj["dst_addr"]) + '"'
                self.logger.info("update_room_device_option resp query = " + query)
                self.mysql_handler_personnal_query(query)
            else:
                query += 'valeur="' + str(json_obj["value"]) + '" WHERE daemon_id=' + str(daemon_id)
                query += ' AND addr_plus="' + str(json_obj["dst_addr"]) + '"'
                self.logger.info("update_room_device_option resp query = " + query)
                self.mysql_handler_personnal_query(query)

    def update_room_device_option_write_short(self, json_obj, daemon_id):
        """
        Update of the table room_device_option with short KNX value
        """
        query = "SELECT option_id, room_device.room_device_id FROM "
        query += "room_device_option JOIN room_device ON "
        query += "room_device_option.room_device_id=room_device.room_device_id WHERE "
        query += "daemon_id=" + str(daemon_id) + ' AND room_device_option.addr="'
        query += str(json_obj["dst_addr"]) + '"'
        self.logger.info("update_room_device_option write_short query : " + query)
        res = self.mysql_handler_personnal_query(query)
        for r in res:
            if (
                int(r[0]) == MasterDaemon.OPTION_ON_OFF
                or int(r[0]) == MasterDaemon.OPTION_UP_DOWN
                or int(r[0]) == MasterDaemon.OPTION_OPEN_CLOSE
            ):
                up = "UPDATE room_device_option SET valeur="
                if json_obj["value"] == 0:
                    up += "0"
                else:
                    up += "255"
                up += " WHERE room_device_id=" + str(r[1]) + " AND option_id=13"
                self.logger.info("update_room_device_option write_short up1: " + up)
                self.mysql_handler_personnal_query(up)
            up = "UPDATE room_device_option SET valeur=" + str(json_obj["value"])
            up += " WHERE room_device_id=" + str(r[1]) + " AND option_id=" + str(r[0]) + ""
            self.logger.info("update_room_device_option write_short up2: " + up)
            self.mysql_handler_personnal_query(up)

    def mysql_handler_personnal_query(self, query):
        """
        Sends personnal query to the database and returns the result
        """
        db = MysqlHandler(self.db_username, self.db_passwd, self.db_dbname)
        res = db.personnal_query(query)
        db.updatedb()
        db.close()
        return res

    def get_daemons(self):
        """
        Retrieves each daemon stored in the database
        """
        db = MysqlHandler(self.db_username, self.db_passwd, self.db_dbname)
        daemons = db.get_datas_from_table_with_names("daemon", ["daemon_id", "name", "serial", "secretkey"])
        return daemons
Example #24
0
class KNXManager:
    """
    KNX management class
    """
    def __init__(self, slave_keys):
        self.knx_function = {
            OPTION_ON_OFF       : self.send_knx_write_short_to_slave,
            OPTION_VAR          : self.send_knx_write_long_to_slave,
            OPTION_UP_DOWN      : self.send_knx_write_short_to_slave,
            OPTION_OPEN_CLOSE   : self.send_knx_write_short_to_slave,
            OPTION_STOP_UP_DOWN : self.send_knx_write_short_to_slave,
            OPTION_SPEED_FAN_0  : self.send_knx_write_speed_fan,
            OPTION_SPEED_FAN_1  : self.send_knx_write_speed_fan,
            OPTION_SPEED_FAN_2  : self.send_knx_write_speed_fan,
            OPTION_SPEED_FAN_3  : self.send_knx_write_speed_fan,
            OPTION_SPEED_FAN_4  : self.send_knx_write_speed_fan,
            OPTION_SPEED_FAN_5  : self.send_knx_write_speed_fan,
            OPTION_SPEED_FAN_6  : self.send_knx_write_speed_fan,
            OPTION_TEMPERATURE_W: self.send_knx_write_temp,
            OPTION_COLOR_R      : self.send_knx_write_long_to_slave,
            OPTION_COLOR_G      : self.send_knx_write_long_to_slave,
            OPTION_COLOR_B      : self.send_knx_write_long_to_slave,
            OPTION_COLOR_W      : self.send_knx_write_long_to_slave
        };
        self.logger = Logger(True, LOG_FILE);
        self.sql = MasterSql();
        self._parser = DaemonConfigParser('/etc/domoleaf/master.conf');
        self.aes_slave_keys = slave_keys;

    def update_room_device_option(self, daemon_id, json_obj):
        """
        Update room_device_option table in database to set new values of the device described by 'json_obj'
        """
        if int(json_obj['type']) == KNX_RESPONSE:
            self.sql.update_room_device_option_resp(json_obj, daemon_id);
        elif int(json_obj['type']) == KNX_WRITE_SHORT:
            self.sql.update_room_device_option_write_short(json_obj, daemon_id);
        elif int(json_obj['type']) == KNX_WRITE_LONG:
            self.sql.update_room_device_option_write_long(json_obj, daemon_id);

    def protocol_knx(self, json_obj, dev, hostname):
        """
        KNX protocol data treatment function
        """
        new_obj = {
            "data": {
                "addr": str(dev['addr_dst']),
                "value": str(json_obj['data']['value']),
                "option_id": str(json_obj['data']['option_id']),
                "room_device_id": str(dev['room_device_id']),
            }
        };
        self.knx_function[int(json_obj['data']['option_id'])](hostname, new_obj);

    def send_json_obj_to_slave(self, json_str, sock, hostname, aes_key, close_flag = True):
        """
        Send 'json_obj' to 'hostname' via 'sock'
        """
        hostname_key = '';
        if '.' in hostname:
            hostname_key = hostname.split('.')[0];
        else:
            hostname_key = hostname;
        AES.key_size = 32;
        aes_IV = AESManager.get_IV();
        encode_obj = AES.new(aes_key, AES.MODE_CBC, aes_IV);
        spaces = 16 - len(json_str) % 16;
        data2 = encode_obj.encrypt(json_str + (spaces * ' '));
        sock.send(bytes(aes_IV, 'utf-8') + data2);
        if close_flag == True:
            sock.close();

    def send_knx_write_speed_fan(self, hostname, json_obj):
        """
        Ask to close all the speed fan before open another
        """
        port = self._parser.getValueFromSection('connect', 'port');
        if not port:
            sys.exit(4);
        if json_obj['data']['value'] == '1':
            query =  'SELECT option_id, addr, dpt_id ';
            query += 'FROM room_device_option ';
            query += 'WHERE room_device_id=' + str(json_obj['data']['room_device_id']) + ' AND ';
            query += 'option_id IN(400, 401, 402, 403, 404, 405, 406) AND status=1';
            res = self.sql.mysql_handler_personnal_query(query);
            for line in res:
                if str(line[2]) == "51" and str(line[0]) == str(json_obj['data']['option_id']):
                    sock = socket.create_connection((hostname, port));
                    val = str(line[0]).split('40')[1];
                    json_str = json.JSONEncoder().encode(
                        {
                            "packet_type": "knx_write_long",
                            "addr_to_send": line[1],
                            "value": val
                        }
                    );
                    self.send_json_obj_to_slave(json_str, sock, hostname, self.aes_slave_keys[hostname]);
                    sock.close();
                    return;
                if str(line[2]) == "2" and str(line[0]) != str(json_obj['data']['option_id']):
                    sock = socket.create_connection((hostname, port));
                    json_str = json.JSONEncoder().encode(
                        {
                            "packet_type": "knx_write_short",
                            "addr_to_send": line[1],
                            "value": "0"
                        }
                    );
                    self.send_json_obj_to_slave(json_str, sock, hostname, self.aes_slave_keys[hostname]);
                    sock.close();

        sock = socket.create_connection((hostname, port));
        json_str = json.JSONEncoder().encode(
            {
                "packet_type": "knx_write_short",
                "addr_to_send": json_obj['data']['addr'],
                "value": json_obj['data']['value']
            }
        );
        self.send_json_obj_to_slave(json_str, sock, hostname, self.aes_slave_keys[hostname]);

    def send_knx_write_temp(self, hostname, json_obj):
        """
        Converts absolute value of temperature (Celsius) in 2 hexadecimal values for
        sending to KNX device
        """
        port = self._parser.getValueFromSection('connect', 'port');
        if not port:
            sys.exit(4);
        sock = socket.create_connection((hostname, port));
        val_str = json_obj['data']['value'];
        if '.' in val_str:
            val_str = val_str.split('.')[0];
        value = utils.convert_temperature_reverse(int(val_str));
        json_str = json.JSONEncoder().encode(
            {
                "packet_type": "knx_write_temp",
                "addr_to_send": json_obj['data']['addr'],
                "value": value[0] + ' ' + value[1]
            }
        );
        self.send_json_obj_to_slave(json_str, sock, hostname, self.aes_slave_keys[hostname]);

    def send_knx_write_long_to_slave(self, hostname, json_obj):
        """
        Constructs long write request and sends it to 'hostname'
        """
        port = self._parser.getValueFromSection('connect', 'port');
        if not port:
            sys.exit(4);
        sock = socket.create_connection((hostname, port));
        json_str = json.JSONEncoder().encode(
            {
                "packet_type": "knx_write_long",
                "addr_to_send": json_obj['data']['addr'],
                "value": hex(int(json_obj['data']['value']))
            }
        );
        self.send_json_obj_to_slave(json_str, sock, hostname, self.aes_slave_keys[hostname]);

    def send_knx_write_short_to_slave(self, hostname, json_obj):
        """
        Constructs short write request and sends it to 'hostname'
        """
        port = self._parser.getValueFromSection('connect', 'port');
        if not port:
            sys.exit(4);
        sock = socket.create_connection((hostname, port));
        json_str = json.JSONEncoder().encode(
            {
                "packet_type": "knx_write_short",
                "addr_to_send": json_obj['data']['addr'],
                "value": json_obj['data']['value']
            }
        );
        self.send_json_obj_to_slave(json_str, sock, hostname, self.aes_slave_keys[hostname]);

    def send_knx_read_request_to_slave(self, hostname, json_obj):
        """
        Constructs short read request and sends it to 'hostname'
        """
        port = self._parser.getValueFromSection('connect', 'port');
        if not port:
            sys.exit(4);
        sock = socket.create_connection((hostname, port));
        json_str = json.JSONEncoder().encode(
            {
                "packet_type": "knx_read_request",
                "addr_to_read": json_obj['data']['addr']
            }
        );
        self.send_json_obj_to_slave(json_str, sock, hostname, self.aes_slave_keys[hostname]);
Example #25
0
class SlaveDaemon:
    """
    Main slave class
    It does communication between different monitors (KNX, EnOcean... in C) and the masters (servers)
    """
    def __init__(self, log_flag):
        self.logger = Logger(log_flag, LOG_FILE);
        self.logger.info('Started Greenleaf Slave daemon');
        print('######## SLAVE DAEMON #######')
        self.connected_masters = {};
        self.connected_knx = [];
        self.connected_enocean = [];
        self.clients = [];
        self._scanner = Scanner(HOST_CONF_FILE);
        self._scanner.scan(False);
        self._hostlist = self._scanner._HostList;
        self._parser = DaemonConfigParser(SLAVE_CONF_FILE);
        self.encrypt_keys = {};
        self.knx_sock = None;
        self.master_sock = None;
        self.enocean_sock = None;
        self.private_aes = hashlib.md5(self._parser.getValueFromSection('personnal_key', 'aes').encode()).hexdigest();
        self.functions = {
            KNX_READ_REQUEST    : self.knx_read_request,
            KNX_WRITE_SHORT     : self.knx_write_short,
            KNX_WRITE_LONG      : self.knx_write_long,
            KNX_WRITE_TEMP      : self.knx_write_temp,
            CHECK_SLAVE         : self.check_slave,
            MONITOR_IP          : self.monitor_ip,
            DATA_UPDATE         : self.update
        };

    def update(self, json_obj, connection):
        call(['apt-get', 'update']);
        call(['apt-get', 'install', 'glslave', '-y']);
        version_file = open('/etc/greenleaf/.glslave.version', 'r');
        if not version_file:
            self.logger.error('/etc/greenleaf/.glslave.version: no such file or directory');
            print('/etc/greenleaf/.glslave.version: no such file or directory');
            return;
        version = version_file.read();
        if '\n' in version:
            version = version.split('\n')[0];
        json_str = '{"packet_type": "update_finished", "aes_pass": "******", "new_version": ' + version + '}'
        encrypt_IV = AESManager.get_IV();
        json_str = json_str + (' ' * (320 - len(json_str)))
        encode_obj = AES.new(self.private_aes, AES.MODE_CBC, encrypt_IV);
        data = encode_obj.encrypt(json_str);
        # faut ouvrir une nouvelle socket pour envoyer la nouvelle version
        # connection.send(bytes(encrypt_IV, 'utf-8') + data);

    def run(self):
        """
        Initialization of the sockets for listenning incomming connections.
        Calls the loop function.
        """
        self.run = True;
        self.knx_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM);
        self.master_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM);
        self.enocean_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM);
        port = self._parser.getValueFromSection(SLAVE_CONF_KNX_SECTION, SLAVE_CONF_KNX_PORT_ENTRY);
        if not port:
            sys.exit(2);
        port_master = self._parser.getValueFromSection(SLAVE_CONF_LISTEN_SECTION, SLAVE_CONF_LISTEN_PORT_ENTRY);
        if not port_master:
            sys.exit(2);
        port_enocean = self._parser.getValueFromSection(SLAVE_CONF_ENOCEAN_SECTION, SLAVE_CONF_ENOCEAN_PORT_ENTRY);
        if not port_enocean:
            sys.exit(2);
        self.knx_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1);
        self.master_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1);
        self.enocean_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1);
        self.knx_sock.bind(('', int(port)));
        self.master_sock.bind(('', int(port_master)));
        self.enocean_sock.bind(('', int(port_enocean)));
        self.knx_sock.listen(MAX_KNX);
        self.master_sock.listen(MAX_MASTERS);
        self.enocean_sock.listen(MAX_ENOCEAN);
        self.loop();

    def accept_knx(self):
        """
        Get available sockets for reading on the KNX socket.
        """
        rlist, wlist, elist = select.select([self.knx_sock], [], [], SELECT_TIMEOUT);
        for connection in rlist:
            new_knx, addr = connection.accept();
            self.connected_knx.append(new_knx);
        self.receive_from_knx(self.connected_knx);

    def accept_masters(self):
        """
        Get available sockets for reading on the master socket.
        """
        rlist, wlist, elist = select.select([self.master_sock], [], [], SELECT_TIMEOUT);
        masters_socks = [];
        for item in rlist:
            new_conn, addr = item.accept();
            masters_socks.append(new_conn);
        self.receive_from_masters(masters_socks);

    def accept_enocean(self):
        """
        Get available sockets for reading on the EnOcean socket.
        """
        rlist, wlist, elist = select.select([self.enocean_sock], [], [], SELECT_TIMEOUT);
        enocean_socks = [];
        for item in rlist:
            new_conn, addr = item.accept();
            enocean_socks.append(new_conn);
            self.connected_enocean.append(new_conn);
        self.receive_from_enocean(enocean_socks);

    def parse_data(self, data, connection):
        """
        Calls the wanted function with the packet_type described in 'data' (JSON syntax)
        """
        json_obj = json.JSONDecoder().decode(data);
        print(json_obj);
        if json_obj['packet_type'] in self.functions.keys():
            self.functions[json_obj['packet_type']](json_obj, connection);
        else:
            raise Exception(str(json_obj['packet_type']) + ": is not a valid packet type");

    def knx_read_request(self, json_obj, connection):
        """
        System call of "groupread" with parameters.
        """
        call([CALL_GROUPREAD, EIB_URL, json_obj['addr_to_read']]);

    def knx_write_temp(self, json_obj, connection):
        """
        System call of "groupwrite" with parameters.
        Almost the same as "knx_write_long" function, except that parameters are not the same
        """
        val = json_obj['value'].split(' ')
        call([CALL_GROUPWRITE, EIB_URL, json_obj['addr_to_send'], val[0], val[1]]);

    def knx_write_short(self, json_obj, connection):
        """
        System call of "groupswrite" with parameters.
        """
        call([CALL_GROUPSWRITE, EIB_URL, json_obj['addr_to_send'], str(json_obj['value'])]);

    def knx_write_long(self, json_obj, connection):
        """
        System call of "groupwrite" with parameters.
        """
        call([CALL_GROUPWRITE, EIB_URL, json_obj['addr_to_send'], str(json_obj['value'])]);

    def receive_from_masters(self, masters_to_read):
        """
        Read data comming from masters and call "parse_data" function.
        """
        for master in masters_to_read:
            data = master.recv(4096);
            decrypt_IV = data[:16].decode();
            decode_obj = AES.new(self.private_aes, AES.MODE_CBC, decrypt_IV);
            data2 = decode_obj.decrypt(data[16:]);
            self.parse_data(data2.decode(), master);

    def receive_from_knx(self, knx_to_read):
        """
        Read data from monitor KNX and transmits to master.
        """
        for knx in knx_to_read:
            data = knx.recv(TELEGRAM_LENGTH);
            if data:
                self.send_knx_data_to_masters(data);
            else:
                if knx in self.connected_knx:
                    knx.close();
                    self.connected_knx.remove(knx);

    def receive_from_enocean(self, enocean_to_read):
        """
        Read data from monitor EnOcean and transmits to master.
        """
        for enocean in enocean_to_read:
            data = enocean.recv(4096);
            if data:
                self.send_enocean_data_to_masters(data);
            else:
                if enocean in self.connected_enocean:
                    enocean.close();
                    self.connected_enocean.remove(enocean);

    def check_slave(self, json_obj, connection):
        """
        Callback called each time a check_slave packet is received.
        Used to confirm the existence of this daemon.
        """
        print("===== CHECK SLAVE =====");
        print(json_obj);
        print("=======================");
        json_str = '{"packet_type": "check_slave", "aes_pass": "******"}'
        master_hostname = str(json_obj['sender_name']);
        encrypt_IV = AESManager.get_IV();
        json_str = json_str + (' ' * (320 - len(json_str)))
        encode_obj = AES.new(self.private_aes, AES.MODE_CBC, encrypt_IV);
        data = encode_obj.encrypt(json_str);
        connection.send(bytes(encrypt_IV, 'utf-8') + data);

    def monitor_ip(self, json_obj, connection):
        """
        Re scan the local network to refresh hostlist.
        """
        self._scanner.scan(True);
        self._hostlist = self._scanner._HostList;

    def loop(self):
        """
        Main daemon loop.
        """
        while self.run:
            try:
                self.accept_knx();
            except Exception as e:
                frameinfo = getframeinfo(currentframe());
                self.logger.error('in loop accept_knx: ' + str(e));
                print('in loop accept_knx: ' + str(e));
            except KeyboardInterrupt as e:
                frameinfo = getframeinfo(currentframe());
                self.logger.error('in loop: Keyboard interrupt');
            try:
                self.accept_masters();
            except Exception as e:
                frameinfo = getframeinfo(currentframe());
                self.logger.error('in loop accept_masters: ' + str(e));
                print('in loop accept_masters: ' + str(e));
            except KeyboardInterrupt as e:
                frameinfo = getframeinfo(currentframe());
                self.logger.error('in loop: Keyboard interrupt');
            try:
                self.accept_enocean();
            except Exception as e:
                frameinfo = getframeinfo(currentframe());
                self.logger.error('in loop accept_enocean: ' + str(e));
                print('in loop accept_enocean: ' + str(e));
            except KeyboardInterrupt as e:
                frameinfo = getframeinfo(currentframe());
                self.logger.error('in loop: Keyboard interrupt');

    def stop(self):
        """
        Stop the daemon and closes all sockets.
        """
        for name, sock in self.connected_masters.items():
            sock.close();
        for knx in self.connected_knx:
            knx.close();
        self.knx_sock.close();

    def connect_to_masters(self):
        """
        Stored every device on network which have his hostname beginning by "MD3" and stores it
        in the self.connected_masters dict(), with hostnames as keys and sockets freshly open as values.
        """
        self.connected_masters = {};
        for host in self._hostlist:
            if MASTER_NAME_PREFIX in host._Hostname:
                port = self._parser.getValueFromSection(SLAVE_CONF_CONNECT_SECTION, SLAVE_CONF_CONNECT_PORT_ENTRY);
                if not port:
                    self.logger.error('in connect_to_masters: No ' + SLAVE_CONF_CONNECT_PORT_ENTRY + ' in ' + SLAVE_CONF_CONNECT_SECTION + ' section or maybe no such ' + SLAVE_CONF_CONNECT_SECTION + ' defined');
                    sys.exit(1);
                try:
                    self.logger.info('Connecting to ' + str(host._IpAddr) + ":" + str(port));
                    sock = socket.create_connection((host._IpAddr, port));
                    hostname = host._Hostname.split('.')[0];
                    self.connected_masters[host._Hostname] = sock;
                except Exception as e:
                    frameinfo = getframeinfo(currentframe());
                    self.logger.error('in connect_to_masters: ' + str(e));
                    pass;

    def send_knx_data_to_masters(self, data):
        """
        Converts 'data' from bytes to a clear KNX datagran, and sends it to available slaves.
        """
        ctrl = int(data[0]);
        src_addr = int.from_bytes(data[1:3], byteorder='big');
        dst_addr = int.from_bytes(data[3:5], byteorder='big');
        data_len = int.from_bytes(data[5:6], byteorder='big');
        telegram_data = data[6:7 + data_len];
        typ = -1;
        value = 0;
        if telegram_data[1] & 0xC0 == 0x00:             # read
            typ = 0;
        elif telegram_data[1] & 0xC0 == 0x40:           # resp
            typ = 1;
            if data_len == 2:
                value = int(telegram_data[1] & 0x0f);
            elif data_len > 2:
                value = int.from_bytes(telegram_data[2:data_len], byteorder='big');
        elif telegram_data[1] & 0xC0 == 0x80:           # write
            typ = 2;
            if data_len == 2:
                value = int(telegram_data[1] & 0x0f);
            elif data_len > 2:
                typ = 3;
                value = int.from_bytes(telegram_data[2:data_len], byteorder='big');
        json_str = json.JSONEncoder().encode(
            {
                "packet_type": "monitor_knx",
                "type": typ,
                "src_addr": individual2string(src_addr),
                "dst_addr": group2string(dst_addr),
                "date": str(time.time()).split('.')[0],
                "value": value,
                "sender_name": socket.gethostname()
            }
        );
        print('===== SENDING KNX DATA =====')
        print(json_str)
        print('============================')
        print()
        self.send_data_to_all_masters(json_str);

    def send_enocean_data_to_masters(self, data):
        """
        Converts 'data' from bytes to a clear EnOcean datagran, and sends it to available slaves.
        """
        self.connect_to_masters();
        if (data[4] == PACKET_TYPE_RADIO_ERP1): # si le packet_type == radio_erp1
            data_len = int.from_bytes(data[1:2], byteorder='big');
            opt_data_len = int(data[3]);
            print(str(data_len) + ' - ' + str(opt_data_len));
            src_str = "%X" % int.from_bytes(data[1+data_len:5+data_len], byteorder='big');
            if len(src_str) < 8:
                src_str = "0" + src_str;
            json_dict = {
                "packet_type": "monitor_enocean",
                "src_addr": src_str,
                "dst_addr": "%X" % int.from_bytes(data[261:265 + opt_data_len], byteorder='big'),
                "date": str(time.time()).split('.')[0],
                "sender_name": socket.gethostname(),
                "type": int(data[6])
            };
            if data[6] == RORG_NORMAL:
                json_dict['value'] = int(data[7]);
            elif data[6] == RORG_TEMPERATURE:
                json_dict['value'] = float(40 - ((40 / 255) * int(data[9])));
            json_str = json.JSONEncoder().encode(json_dict);
            self.send_data_to_all_masters(json_str);

    def send_data_to_all_masters(self, json_str):
        """
        Sends a string 'json_str' to available slaves on network.
        """
        self.connect_to_masters();
        # ici envoyer a tous les masters
        for name in self.connected_masters.keys():
            try:
                master = self.connected_masters[name];
                AES.key_size = 32;
                aes_IV = AESManager.get_IV();
                encode_obj = AES.new(self.private_aes, AES.MODE_CBC, aes_IV);
                data2 = encode_obj.encrypt(json_str + ((320 - len(json_str)) * ' '));
                print("Sending data to " + name);
                master.send(bytes(aes_IV, 'utf-8') + data2);
                print('Done.');
                master.close();
            except KeyError as e:
                self.logger.error('in send_data_to_all_masters: ' + str(e));
                print(e);
                pass;
Example #26
0
class KNXManager:
    """
    KNX management class
    """
    def __init__(self, slave_keys):
        self.logger = Logger(True, LOG_FILE);
        self.sql = MasterSql();
        self._parser = DaemonConfigParser('/etc/domoleaf/master.conf');
        self.aes_slave_keys = slave_keys;

    def update_room_device_option(self, daemon_id, json_obj):
        """
        Update room_device_option table in database to set new values of the device described by 'json_obj'
        """
        if int(json_obj['type']) == KNX_RESPONSE:
            return self.sql.update_room_device_option_resp(json_obj, daemon_id);
        elif int(json_obj['type']) == KNX_WRITE_SHORT:
            return self.sql.update_room_device_option_write_short(json_obj, daemon_id);
        elif int(json_obj['type']) == KNX_WRITE_LONG:
            return self.sql.update_room_device_option_write_long(json_obj, daemon_id);

    def send_json_obj_to_slave(self, json_str, sock, hostname, aes_key, close_flag = True):
        """
        Send 'json_obj' to 'hostname' via 'sock'
        """
        hostname_key = '';
        if '.' in hostname:
            hostname_key = hostname.split('.')[0];
        else:
            hostname_key = hostname;
        AES.key_size = 32;
        aes_IV = AESManager.get_IV();
        encode_obj = AES.new(aes_key, AES.MODE_CBC, aes_IV);
        spaces = 16 - len(json_str) % 16;
        data2 = encode_obj.encrypt(json_str + (spaces * ' '));
        sock.send(bytes(aes_IV, 'utf-8') + data2);
        if close_flag == True:
            sock.close();

    def send_knx_write_speed_fan(self, json_obj, dev, hostname):
        """
        Ask to close all the speed fan before open another
        """
        port = self._parser.getValueFromSection('connect', 'port');
        if not port:
            sys.exit(4);
        if json_obj['data']['value'] == '1':
            query =  'SELECT option_id, addr, dpt_id ';
            query += 'FROM room_device_option ';
            query += 'WHERE room_device_id=' + str(dev['room_device_id']) + ' AND ';
            query += 'option_id IN(400, 401, 402, 403, 404, 405, 406) AND status=1';
            res = self.sql.mysql_handler_personnal_query(query);
            for line in res:
                if str(line[2]) == "51" and str(line[0]) == str(json_obj['data']['option_id']):
                    sock = socket.create_connection((hostname, port));
                    val = str(line[0]).split('40')[1];
                    json_str = json.JSONEncoder().encode(
                        {
                            "packet_type": "knx_write_long",
                            "addr_to_send": line[1],
                            "value": val
                        }
                    );
                    self.send_json_obj_to_slave(json_str, sock, hostname, self.aes_slave_keys[hostname]);
                    sock.close();
                    return;
                if str(line[2]) == "2" and str(line[0]) != str(json_obj['data']['option_id']):
                    sock = socket.create_connection((hostname, port));
                    json_str = json.JSONEncoder().encode(
                        {
                            "packet_type": "knx_write_short",
                            "addr_to_send": line[1],
                            "value": "0"
                        }
                    );
                    self.send_json_obj_to_slave(json_str, sock, hostname, self.aes_slave_keys[hostname]);
                    sock.close();
        sock = socket.create_connection((hostname, port));
        json_str = json.JSONEncoder().encode(
            {
                "packet_type": "knx_write_short",
                "addr_to_send": str(dev['addr_dst']),
                "value": json_obj['data']['value']
            }
        );
        self.send_json_obj_to_slave(json_str, sock, hostname, self.aes_slave_keys[hostname]);

    def send_knx_write_temp(self, json_obj, dev, hostname):
        """
        Converts absolute value of temperature (Celsius) in 2 hexadecimal values for
        sending to KNX device
        """
        port = self._parser.getValueFromSection('connect', 'port');
        if not port:
            sys.exit(4);
        sock = socket.create_connection((hostname, port));
        val_str = json_obj['data']['value'];
        if ',' in val_str:
            val_str = val_str.replace(',', '.')
        value = utils.convert_temperature_reverse(float(val_str));
        json_str = json.JSONEncoder().encode(
            {
                "packet_type": "knx_write_temp",
                "addr_to_send": str(dev['addr_dst']),
                "value": value[0] + ' ' + value[1]
            }
        );
        self.send_json_obj_to_slave(json_str, sock, hostname, self.aes_slave_keys[hostname]);

    def send_knx_write_long_to_slave(self, json_obj, dev, hostname):
        """
        Constructs long write request and sends it to 'hostname'
        """
        port = self._parser.getValueFromSection('connect', 'port');
        if not port:
            sys.exit(4);
        sock = socket.create_connection((hostname, port));
        json_str = json.JSONEncoder().encode(
            {
                "packet_type": "knx_write_long",
                "addr_to_send": str(dev['addr_dst']),
                "value": hex(int(json_obj['data']['value']))
            }
        );
        self.send_json_obj_to_slave(json_str, sock, hostname, self.aes_slave_keys[hostname]);

    def send_knx_write_short_to_slave(self, json_obj, dev, hostname):
        """
        Constructs short write request and sends it to 'hostname'
        """
        port = self._parser.getValueFromSection('connect', 'port');
        if not port:
            sys.exit(4);
        sock = socket.create_connection((hostname, port));
        json_str = json.JSONEncoder().encode(
            {
                "packet_type": "knx_write_short",
                "addr_to_send": str(dev['addr_dst']),
                "value": json_obj['data']['value']
            }
        );
        self.send_json_obj_to_slave(json_str, sock, hostname, self.aes_slave_keys[hostname]);

    def send_knx_read_request_to_slave(self, hostname, json_obj):
        """
        Constructs short read request and sends it to 'hostname'
        """
        port = self._parser.getValueFromSection('connect', 'port');
        if not port:
            sys.exit(4);
        sock = socket.create_connection((hostname, port));
        json_str = json.JSONEncoder().encode(
            {
                "packet_type": "knx_read_request",
                "addr_to_read": json_obj['data']['addr']
            }
        );
        self.send_json_obj_to_slave(json_str, sock, hostname, self.aes_slave_keys[hostname]);
    
    def send_on(self, json_obj, dev, hostname):
        """
        Ask to close all the speed fan before open another
        """
        port = self._parser.getValueFromSection('connect', 'port');
        if not port:
            sys.exit(4);
        sock = socket.create_connection((hostname, port));
        json_str = json.JSONEncoder().encode(
            {
                "packet_type": "knx_write_short",
                "addr_to_send": str(dev['addr_dst']),
                "value": "1"
            }
        );
        self.send_json_obj_to_slave(json_str, sock, hostname, self.aes_slave_keys[hostname]);
        sock.close();
        return;
    
    def send_to_thermostat(self, json_obj, dev, hostname):
        port = self._parser.getValueFromSection('connect', 'port');
        if not port:
            sys.exit(4);
        
        if json_obj['data']['option_id'] == '412':
            val = 1;
        elif json_obj['data']['option_id'] == '413':
            val = 2;
        elif json_obj['data']['option_id'] == '414':
            val = 4;
        elif json_obj['data']['option_id'] == '415':
            val = 8;
        elif json_obj['data']['option_id'] == '416':
            val = 16;
        elif json_obj['data']['option_id'] == '417':
            val = 32;
        else:
            val = 0
        
        if val > 0:
            json_str = json.JSONEncoder().encode(
                {
                    "packet_type": "knx_write_long",
                    "addr_to_send": hex(int(dev['addr_dst'])),
                    "value": val
                }
            );
            sock = socket.create_connection((hostname, port));
            self.send_json_obj_to_slave(json_str, sock, hostname, self.aes_slave_keys[hostname]);
            sock.close();
            return;
    
    def send_clim_mode(self, json_obj, dev, hostname):
        if json_obj['data']['option_id'] == '425': #Auto
            val = 0
        elif json_obj['data']['option_id'] == '426': #Heat
            val = 1
        elif json_obj['data']['option_id'] == '427': #Morning Warmup
            val = 2
        elif json_obj['data']['option_id'] == '428': #Cool
            val = 3
        elif json_obj['data']['option_id'] == '429': #Night Purge
            val = 4
        elif json_obj['data']['option_id'] == '430': #Precool
            val = 5
        elif json_obj['data']['option_id'] == '431': #Off
            val = 6
        elif json_obj['data']['option_id'] == '432': #Test
            val = 7
        elif json_obj['data']['option_id'] == '433': #Emergency Heat
            val = 8
        elif json_obj['data']['option_id'] == '434': #Fan only
            val = 9
        elif json_obj['data']['option_id'] == '435': #Free Cool
            val = 10
        elif json_obj['data']['option_id'] == '436': #Ice
            val = 11
        else:
            val = -1
        
        if val >= 0:
            json_str = json.JSONEncoder().encode(
                {
                    "packet_type": "knx_write_long",
                    "addr_to_send": str(dev['addr_dst']),
                    "value":  hex(int(val))
                }
            );
            sock = socket.create_connection((hostname, port));
            self.send_json_obj_to_slave(json_str, sock, hostname, self.aes_slave_keys[hostname]);
            sock.close();
            return;

    def send_knx_write_percent(self, json_obj, dev, hostname):
        port = self._parser.getValueFromSection('connect', 'port');
        if not port:
            sys.exit(4);
        sock = socket.create_connection((hostname, port));
        json_str = json.JSONEncoder().encode(
            {
                "packet_type": "knx_write_long",
                "addr_to_send": str(dev['addr_dst']),
                "value": hex(255*int(json_obj['data']['value'])/100)
            }
        );
        self.send_json_obj_to_slave(json_str, sock, hostname, self.aes_slave_keys[hostname]);