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);
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)
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);
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()
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, )
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]);
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)
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;
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;
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]);
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;
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);
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'));
#!/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))
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();
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)
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;
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
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)
# 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)
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;
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
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]);
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;
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]);