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 }
def master_conf_initdb(): file = DaemonConfigParser(MASTER_CONF_FILE_TO) # mysql password password = "".join( random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(128) ) password = sha1(password.encode("utf-8")) file.writeValueFromSection("mysql", "password", password.hexdigest()) os.system( "sed -i \"s/define('DB_PASSWORD', 'domoleaf')/define('DB_PASSWORD', '" + password.hexdigest() + "')/g\" /etc/domoleaf/www/config.php" ) # mysql user query1 = 'DELETE FROM user WHERE User="******"' query2 = 'DELETE FROM db WHERE User="******"' query3 = ( "INSERT INTO user (Host, User, Password) VALUES ('%', 'domoleaf', PASSWORD('" + password.hexdigest() + "'));" ) query4 = 'INSERT INTO db (Host, Db, User, Select_priv, Insert_priv, Update_priv, Delete_priv, Create_priv, Drop_priv, Grant_priv, References_priv, Index_priv, Alter_priv, Create_tmp_table_priv, Lock_tables_priv, Create_view_priv, Show_view_priv, Create_routine_priv, Alter_routine_priv, Execute_priv, Event_priv, Trigger_priv) VALUES ("%","domoleaf","domoleaf","Y","Y","Y","Y","Y","Y","Y","Y","Y","Y","Y","Y","Y","Y","Y","Y","Y","Y","Y");' query5 = "FLUSH PRIVILEGES" Popen( ["mysql", "--defaults-file=/etc/mysql/debian.cnf", "mysql", "-e", query1], stdin=PIPE, stdout=PIPE, stderr=PIPE, bufsize=-1, ) Popen( ["mysql", "--defaults-file=/etc/mysql/debian.cnf", "mysql", "-e", query2], stdin=PIPE, stdout=PIPE, stderr=PIPE, bufsize=-1, ) Popen( ["mysql", "--defaults-file=/etc/mysql/debian.cnf", "mysql", "-e", query3], stdin=PIPE, stdout=PIPE, stderr=PIPE, bufsize=-1, ) Popen( ["mysql", "--defaults-file=/etc/mysql/debian.cnf", "mysql", "-e", query4], stdin=PIPE, stdout=PIPE, stderr=PIPE, bufsize=-1, ) Popen( ["mysql", "--defaults-file=/etc/mysql/debian.cnf", "mysql", "-e", query5], stdin=PIPE, stdout=PIPE, stderr=PIPE, bufsize=-1, )
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;
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 }
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 __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(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.cron_sock = None; self.private_aes = hashlib.md5(self._parser.getValueFromSection('personnal_key', 'aes').encode()).hexdigest(); 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.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 __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 __init__(self, _id_elem = 0, _option_id = 0, _debug = False): 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);
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, }
def slave_conf_init(): file = DaemonConfigParser(SLAVE_CONF_FILE_TO); #KEY KEY = ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(128)) KEY = md5(KEY.encode('utf-8')).hexdigest() file.writeValueFromSection('personnal_key', 'aes', KEY); #KNX Interface knx_edit = 'KNXD_OPTS="-e 1.0.254 -D -T -S -b ' if os.path.exists('/dev/ttyAMA0'): knx_edit = knx_edit + 'tpuarts:/dev/ttyAMA0"'; elif os.path.exists('/dev/ttyS0'): knx_edit = knx_edit + 'tpuarts:/dev/ttyS0"'; else: knx_edit = knx_edit + 'ipt:127.0.0.1"'; conf_knx = open('/etc/knxd.conf', 'w'); conf_knx.write(knx_edit + '\n'); conf_knx.close();
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 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]);
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 master_conf_initdb(): file = DaemonConfigParser(MASTER_CONF_FILE_TO); #mysql password password = ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(128)) password = sha1(password.encode('utf-8')) file.writeValueFromSection('mysql', 'password', password.hexdigest()); os.system('sed -i "s/define(\'DB_PASSWORD\', \'domoleaf\')/define(\'DB_PASSWORD\', \''+password.hexdigest()+'\')/g" /etc/domoleaf/www/config.php') #mysql user query1 = 'DELETE FROM user WHERE User="******"'; query2 = 'DELETE FROM db WHERE User="******"'; query3 = 'INSERT INTO user (Host, User, Password) VALUES (\'%\', \'domoleaf\', PASSWORD(\''+password.hexdigest()+'\'));'; query4 = 'INSERT INTO db (Host, Db, User, Select_priv, Insert_priv, Update_priv, Delete_priv, Create_priv, Drop_priv, Grant_priv, References_priv, Index_priv, Alter_priv, Create_tmp_table_priv, Lock_tables_priv, Create_view_priv, Show_view_priv, Create_routine_priv, Alter_routine_priv, Execute_priv, Event_priv, Trigger_priv) VALUES ("%","domoleaf","domoleaf","Y","Y","Y","Y","Y","Y","Y","Y","Y","Y","Y","Y","Y","Y","Y","Y","Y","Y","Y");'; query5 = 'FLUSH PRIVILEGES'; Popen(['mysql', '--defaults-file=/etc/mysql/debian.cnf', 'mysql', '-e', query1], stdin=PIPE, stdout=PIPE, stderr=PIPE, bufsize=-1); Popen(['mysql', '--defaults-file=/etc/mysql/debian.cnf', 'mysql', '-e', query2], stdin=PIPE, stdout=PIPE, stderr=PIPE, bufsize=-1); Popen(['mysql', '--defaults-file=/etc/mysql/debian.cnf', 'mysql', '-e', query3], stdin=PIPE, stdout=PIPE, stderr=PIPE, bufsize=-1); Popen(['mysql', '--defaults-file=/etc/mysql/debian.cnf', 'mysql', '-e', query4], stdin=PIPE, stdout=PIPE, stderr=PIPE, bufsize=-1); Popen(['mysql', '--defaults-file=/etc/mysql/debian.cnf', 'mysql', '-e', query5], stdin=PIPE, stdout=PIPE, stderr=PIPE, bufsize=-1);
def slave_conf_init(): file = DaemonConfigParser(SLAVE_CONF_FILE_TO) #KEY KEY = ''.join( random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(128)) KEY = md5(KEY.encode('utf-8')).hexdigest() file.writeValueFromSection('personnal_key', 'aes', KEY) #KNX Interface knx_edit = 'KNXD_OPTS="-e 1.0.254 -D -T -S -b ' if os.path.exists('/dev/ttyAMA0'): knx_edit = knx_edit + 'tpuarts:/dev/ttyAMA0"' elif os.path.exists('/dev/ttyS0'): knx_edit = knx_edit + 'tpuarts:/dev/ttyS0"' else: knx_edit = knx_edit + 'ipt:127.0.0.1"' conf_knx = open('/etc/knxd.conf', 'w') conf_knx.write(knx_edit + '\n') conf_knx.close()
def __init__(self, slave_keys): ## Logger object for formatting and printing self.logger = Logger(DEBUG_MODE, LOG_FILE) ## SQL manager for the master daemon self.sql = MasterSql() self._parser = DaemonConfigParser('/etc/domoleaf/master.conf') ## Object containing the AES keys for encrypted communications self.aes_slave_keys = slave_keys ## Array containing functions associated with IDs self.functions_transform = { 0: utils.convert_none, 4: utils.eno_onoff }
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 __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 __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 __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 __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 };
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)
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'));
## @package domomaster # 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" }]
#!/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 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 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 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 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;
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);
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)
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 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 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 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 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 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;