def setup_minimal_master_platform(port): # type: (str) -> None config = ConfigParser() config.read(constants.get_config_file()) platform = Platform.get_platform() Injectable.value(controller_serial=Serial(port, 115200)) if platform == Platform.Type.DUMMY: Injectable.value(maintenance_communicator=None) Injectable.value(master_controller=MasterDummyController()) elif platform in Platform.CoreTypes: from master.core import ucan_communicator _ = ucan_communicator core_cli_serial_port = config.get('OpenMotics', 'cli_serial') Injectable.value(cli_serial=Serial(core_cli_serial_port, 115200)) Injectable.value(master_communicator=CoreCommunicator()) Injectable.value(maintenance_communicator=None) Injectable.value(memory_file=MemoryFile()) Injectable.value(master_controller=MasterCoreController()) elif platform in Platform.ClassicTypes: Injectable.value( eeprom_db=constants.get_eeprom_extension_database_file()) from master.classic import eeprom_extension _ = eeprom_extension Injectable.value(master_communicator=MasterCommunicator()) Injectable.value(maintenance_communicator=None) Injectable.value(master_controller=MasterClassicController()) else: logger.warning('Unhandled master implementation for %s', platform)
shutil.rmtree(plugin_dir + plugin) config_files = constants.get_plugin_configfiles() for config_file in glob.glob(config_files): os.remove(config_file) if __name__ == '__main__': setup_logger() config = ConfigParser() config.read(constants.get_config_file()) Injectable.value(config_db_lock=Lock()) Injectable.value(config_db=constants.get_config_database_file()) Injectable.value(eeprom_db=constants.get_eeprom_extension_database_file()) controller_serial_port = config.get('OpenMotics', 'controller_serial') Injectable.value(controller_serial=Serial(controller_serial_port, 115200)) from gateway import config as config_controller _ = config_controller if Platform.get_platform() == Platform.Type.CORE_PLUS: from gateway.hal import master_controller_core # type: ignore from master_core import maintenance, core_communicator, ucan_communicator # type: ignore _ = master_controller_core, maintenance, core_communicator, ucan_communicator # type: ignore else: from gateway.hal import master_controller_classic # type: ignore from master import maintenance, master_communicator, eeprom_extension # type: ignore _ = master_controller_classic, maintenance, master_communicator, eeprom_extension # type: ignore
def setup_target_platform(target_platform, message_client_name): # type: (str, Optional[str]) -> None config = ConfigParser() config.read(constants.get_config_file()) config_lock = Lock() metrics_lock = Lock() config_database_file = constants.get_config_database_file() # Debugging options try: debug_logger = config.get('OpenMotics', 'debug_logger') if debug_logger: logging.getLogger(debug_logger).setLevel(logging.DEBUG) except NoOptionError: pass # Webserver / Presentation layer try: https_port = int(config.get('OpenMotics', 'https_port')) except NoOptionError: https_port = 443 try: http_port = int(config.get('OpenMotics', 'http_port')) except NoOptionError: http_port = 80 Injectable.value(https_port=https_port) Injectable.value(http_port=http_port) Injectable.value(ssl_private_key=constants.get_ssl_private_key_file()) Injectable.value(ssl_certificate=constants.get_ssl_certificate_file()) # TODO: Clean up dependencies more to reduce complexity # IOC announcements # When below modules are imported, the classes are registerd in the IOC graph. This is required for # instances that are used in @Inject decorated functions below, and is also needed to specify # abstract implementations depending on e.g. the platform (classic vs core) or certain settings (classic # thermostats vs gateway thermostats) from plugins import base from gateway import (metrics_controller, webservice, scheduling, observer, gateway_api, metrics_collector, maintenance_controller, user_controller, pulse_counter_controller, metrics_caching, watchdog, output_controller, room_controller, sensor_controller, shutter_controller, group_action_controller, module_controller, ventilation_controller) from cloud import events _ = (metrics_controller, webservice, scheduling, observer, gateway_api, metrics_collector, maintenance_controller, base, events, user_controller, pulse_counter_controller, metrics_caching, watchdog, output_controller, room_controller, sensor_controller, shutter_controller, group_action_controller, module_controller, ventilation_controller) # IPC message_client = None if message_client_name is not None: message_client = MessageClient(message_client_name) Injectable.value(message_client=message_client) # Cloud API Injectable.value(gateway_uuid=config.get('OpenMotics', 'uuid')) try: parsed_url = urlparse(config.get('OpenMotics', 'vpn_check_url')) except NoOptionError: parsed_url = urlparse('') Injectable.value(cloud_endpoint=parsed_url.hostname) Injectable.value(cloud_port=parsed_url.port) Injectable.value(cloud_ssl=parsed_url.scheme == 'https') Injectable.value(cloud_api_version=0) cloud_url = urlunparse( (parsed_url.scheme, parsed_url.netloc, '', '', '', '')) Injectable.value(cloud_url=cloud_url or None) try: firmware_url = config.get('OpenMotics', 'firmware_url') except NoOptionError: path = '/portal/firmware_metadata' firmware_url = urlunparse( (parsed_url.scheme, parsed_url.netloc, path, '', '', '')) Injectable.value(firmware_url=firmware_url or None) # User Controller Injectable.value(user_db=config_database_file) Injectable.value(user_db_lock=config_lock) Injectable.value(token_timeout=3600) Injectable.value( config={ 'username': config.get('OpenMotics', 'cloud_user'), 'password': config.get('OpenMotics', 'cloud_pass') }) # Metrics Controller Injectable.value(metrics_db=constants.get_metrics_database_file()) Injectable.value(metrics_db_lock=metrics_lock) # Energy Controller try: power_serial_port = config.get('OpenMotics', 'power_serial') except NoOptionError: power_serial_port = '' if power_serial_port: Injectable.value(power_db=constants.get_power_database_file()) Injectable.value(power_store=PowerStore()) # TODO: make non blocking? Injectable.value(power_serial=RS485( Serial(power_serial_port, 115200, timeout=None))) Injectable.value(power_communicator=PowerCommunicator()) Injectable.value(power_controller=PowerController()) Injectable.value(p1_controller=P1Controller()) else: Injectable.value(power_serial=None) Injectable.value(power_store=None) Injectable.value( power_communicator=None) # TODO: remove from gateway_api Injectable.value(power_controller=None) Injectable.value(p1_controller=None) # Pulse Controller Injectable.value(pulse_db=constants.get_pulse_counter_database_file()) # Master Controller try: controller_serial_port = config.get('OpenMotics', 'controller_serial') except NoOptionError: controller_serial_port = '' if controller_serial_port: Injectable.value(controller_serial=Serial( controller_serial_port, 115200, exclusive=True)) if target_platform in [Platform.Type.DUMMY, Platform.Type.ESAFE]: Injectable.value(maintenance_communicator=None) Injectable.value(passthrough_service=None) Injectable.value(master_controller=MasterDummyController()) Injectable.value(eeprom_db=None) from gateway.hal.master_controller_dummy import DummyEepromObject Injectable.value(eeprom_extension=DummyEepromObject()) elif target_platform in Platform.CoreTypes: # FIXME don't create singleton for optional controller? from master.core import ucan_communicator, slave_communicator _ = ucan_communicator, slave_communicator core_cli_serial_port = config.get('OpenMotics', 'cli_serial') Injectable.value(cli_serial=Serial(core_cli_serial_port, 115200)) Injectable.value(passthrough_service=None) # Mark as "not needed" # TODO: Remove; should not be needed for Core Injectable.value( eeprom_db=constants.get_eeprom_extension_database_file()) Injectable.value(master_communicator=CoreCommunicator()) Injectable.value( maintenance_communicator=MaintenanceCoreCommunicator()) Injectable.value(memory_file=MemoryFile()) Injectable.value(master_controller=MasterCoreController()) elif target_platform in Platform.ClassicTypes: # FIXME don't create singleton for optional controller? from master.classic import eeprom_extension _ = eeprom_extension leds_i2c_address = config.get('OpenMotics', 'leds_i2c_address') passthrough_serial_port = config.get('OpenMotics', 'passthrough_serial') Injectable.value( eeprom_db=constants.get_eeprom_extension_database_file()) Injectable.value(leds_i2c_address=int(leds_i2c_address, 16)) if passthrough_serial_port: Injectable.value( passthrough_serial=Serial(passthrough_serial_port, 115200)) from master.classic.passthrough import PassthroughService _ = PassthroughService # IOC announcement else: Injectable.value(passthrough_service=None) Injectable.value(master_communicator=MasterCommunicator()) Injectable.value( maintenance_communicator=MaintenanceClassicCommunicator()) Injectable.value(master_controller=MasterClassicController()) else: logger.warning('Unhandled master implementation for %s', target_platform) if target_platform in [Platform.Type.DUMMY, Platform.Type.ESAFE]: Injectable.value(frontpanel_controller=None) elif target_platform in Platform.CoreTypes: Injectable.value(frontpanel_controller=FrontpanelCoreController()) elif target_platform in Platform.ClassicTypes: Injectable.value(frontpanel_controller=FrontpanelClassicController()) else: logger.warning('Unhandled frontpanel implementation for %s', target_platform) # Thermostats thermostats_gateway_feature = Feature.get_or_none( name='thermostats_gateway') thermostats_gateway_enabled = thermostats_gateway_feature is not None and thermostats_gateway_feature.enabled if target_platform not in Platform.ClassicTypes or thermostats_gateway_enabled: Injectable.value(thermostat_controller=ThermostatControllerGateway()) else: Injectable.value(thermostat_controller=ThermostatControllerMaster())
def build_graph(): config = ConfigParser() config.read(constants.get_config_file()) config_lock = Lock() scheduling_lock = Lock() metrics_lock = Lock() config_database_file = constants.get_config_database_file() # TODO: Clean up dependencies more to reduce complexity # IOC announcements # When below modules are imported, the classes are registerd in the IOC graph. This is required for # instances that are used in @Inject decorated functions below, and is also needed to specify # abstract implementations depending on e.g. the platform (classic vs core) or certain settings (classic # thermostats vs gateway thermostats) from power import power_communicator, power_controller from plugins import base from gateway import (metrics_controller, webservice, scheduling, observer, gateway_api, metrics_collector, maintenance_controller, comm_led_controller, users, pulses, config as config_controller, metrics_caching, watchdog) from cloud import events _ = (metrics_controller, webservice, scheduling, observer, gateway_api, metrics_collector, maintenance_controller, base, events, power_communicator, comm_led_controller, users, power_controller, pulses, config_controller, metrics_caching, watchdog) if Platform.get_platform() == Platform.Type.CORE_PLUS: from gateway.hal import master_controller_core from master_core import maintenance, core_communicator, ucan_communicator from master import eeprom_extension # TODO: Obsolete, need to be removed _ = master_controller_core, maintenance, core_communicator, ucan_communicator else: from gateway.hal import master_controller_classic from master import maintenance, master_communicator, eeprom_extension _ = master_controller_classic, maintenance, master_communicator, eeprom_extension thermostats_gateway_feature = Feature.get_or_none( name='thermostats_gateway') thermostats_gateway_enabled = thermostats_gateway_feature is not None and thermostats_gateway_feature.enabled if Platform.get_platform( ) == Platform.Type.CORE_PLUS or thermostats_gateway_enabled: from gateway.thermostat.gateway import thermostat_controller_gateway _ = thermostat_controller_gateway else: from gateway.thermostat.master import thermostat_controller_master _ = thermostat_controller_master # IPC Injectable.value(message_client=MessageClient('openmotics_service')) # Cloud API parsed_url = urlparse(config.get('OpenMotics', 'vpn_check_url')) Injectable.value(gateway_uuid=config.get('OpenMotics', 'uuid')) Injectable.value(cloud_endpoint=parsed_url.hostname) Injectable.value(cloud_port=parsed_url.port) Injectable.value(cloud_ssl=parsed_url.scheme == 'https') Injectable.value(cloud_api_version=0) # User Controller Injectable.value(user_db=config_database_file) Injectable.value(user_db_lock=config_lock) Injectable.value(token_timeout=3600) Injectable.value( config={ 'username': config.get('OpenMotics', 'cloud_user'), 'password': config.get('OpenMotics', 'cloud_pass') }) # Configuration Controller Injectable.value(config_db=config_database_file) Injectable.value(config_db_lock=config_lock) # Energy Controller power_serial_port = config.get('OpenMotics', 'power_serial') Injectable.value(power_db=constants.get_power_database_file()) if power_serial_port: Injectable.value(power_serial=RS485( Serial(power_serial_port, 115200, timeout=None))) else: Injectable.value(power_serial=None) Injectable.value(power_communicator=None) Injectable.value(power_controller=None) # Pulse Controller Injectable.value(pulse_db=constants.get_pulse_counter_database_file()) # Scheduling Controller Injectable.value( scheduling_db=constants.get_scheduling_database_file()) Injectable.value(scheduling_db_lock=scheduling_lock) # Master Controller controller_serial_port = config.get('OpenMotics', 'controller_serial') Injectable.value( controller_serial=Serial(controller_serial_port, 115200)) if Platform.get_platform() == Platform.Type.CORE_PLUS: from master_core.memory_file import MemoryFile, MemoryTypes core_cli_serial_port = config.get('OpenMotics', 'cli_serial') Injectable.value(cli_serial=Serial(core_cli_serial_port, 115200)) Injectable.value(passthrough_service=None) # Mark as "not needed" Injectable.value( memory_files={ MemoryTypes.EEPROM: MemoryFile(MemoryTypes.EEPROM), MemoryTypes.FRAM: MemoryFile(MemoryTypes.FRAM) }) # TODO: Remove; should not be needed for Core Injectable.value( eeprom_db=constants.get_eeprom_extension_database_file()) else: passthrough_serial_port = config.get('OpenMotics', 'passthrough_serial') Injectable.value( eeprom_db=constants.get_eeprom_extension_database_file()) if passthrough_serial_port: Injectable.value( passthrough_serial=Serial(passthrough_serial_port, 115200)) from master.passthrough import PassthroughService _ = PassthroughService # IOC announcement else: Injectable.value(passthrough_service=None) # Metrics Controller Injectable.value(metrics_db=constants.get_metrics_database_file()) Injectable.value(metrics_db_lock=metrics_lock) # Webserver / Presentation layer Injectable.value(ssl_private_key=constants.get_ssl_private_key_file()) Injectable.value(ssl_certificate=constants.get_ssl_certificate_file())
def restore_full_backup(self, data): """ Restore a full backup containing the master eeprom and the sqlite databases. :param data: The backup to restore. :type data: Tar containing multiple files: master.eep, config.db, scheduled.db, power.db, eeprom_extensions.db, metrics.db and plugins as a string of bytes. :returns: dict with 'output' key. """ import glob import shutil import tempfile import subprocess tmp_dir = tempfile.mkdtemp() tmp_sqlite_dir = '{0}/sqlite'.format(tmp_dir) try: with open('{0}/backup.tar'.format(tmp_dir), 'wb') as backup_file: backup_file.write(data) retcode = subprocess.call( 'cd {0}; tar xf backup.tar'.format(tmp_dir), shell=True) if retcode != 0: raise Exception('The backup tar could not be extracted.') # Check if the sqlite db's are in a folder or not for backwards compatibility src_dir = tmp_sqlite_dir if os.path.isdir( tmp_sqlite_dir) else tmp_dir with open('{0}/master.eep'.format(src_dir), 'r') as eeprom_file: eeprom_content = eeprom_file.read() self.master_restore(eeprom_content) for filename, target in { 'config.db': constants.get_config_database_file(), 'users.db': constants.get_config_database_file(), 'power.db': constants.get_power_database_file(), 'eeprom_extensions.db': constants.get_eeprom_extension_database_file(), 'metrics.db': constants.get_metrics_database_file(), 'gateway.db': constants.get_gateway_database_file() }.items(): source = '{0}/{1}'.format(src_dir, filename) if os.path.exists(source): shutil.copyfile(source, target) # Restore the plugins if there are any backup_plugin_dir = '{0}/plugins'.format(tmp_dir) backup_plugin_content_dir = '{0}/content'.format(backup_plugin_dir) backup_plugin_config_files = '{0}/config/pi_*'.format( backup_plugin_dir) if os.path.isdir(backup_plugin_dir): plugin_dir = constants.get_plugin_dir() plugins = [ name for name in os.listdir(backup_plugin_content_dir) if os.path.isdir( os.path.join(backup_plugin_content_dir, name)) ] for plugin in plugins: dest_dir = '{0}{1}'.format(plugin_dir, plugin) if os.path.isdir(dest_dir): shutil.rmtree(dest_dir) shutil.copytree( '{0}/{1}/'.format(backup_plugin_content_dir, plugin), '{0}{1}'.format(plugin_dir, plugin)) config_files = constants.get_plugin_config_dir() for config_file in glob.glob(backup_plugin_config_files): shutil.copy(config_file, '{0}/'.format(config_files)) return {'output': 'Restore complete'} finally: shutil.rmtree(tmp_dir) # Restart the Cherrypy server after 1 second. Lets the current request terminate. threading.Timer(1, lambda: os._exit(0)).start()
def get_full_backup(self): """ Get a backup (tar) of the master eeprom, the sqlite databases and the plugins :returns: Tar containing multiple files: master.eep, config.db, scheduled.db, power.db, eeprom_extensions.db, metrics.db and plugins as a string of bytes. """ _ = self # Not static for consistency def backup_sqlite_db(input_db_path, backup_db_path): """ Backup an sqlite db provided the path to the db to backup and the backup db. """ # Connect to database connection = sqlite3.connect(input_db_path) cursor = connection.cursor() # Lock database before making a backup cursor.execute('begin immediate') # Make new backup file shutil.copyfile(input_db_path, backup_db_path) # Unlock database connection.rollback() tmp_dir = tempfile.mkdtemp() tmp_sqlite_dir = '{0}/sqlite'.format(tmp_dir) os.mkdir(tmp_sqlite_dir) try: with open('{0}/master.eep'.format(tmp_sqlite_dir), 'w') as eeprom_file: eeprom_file.write(self.get_master_backup()) for filename, source in { 'config.db': constants.get_config_database_file(), 'power.db': constants.get_power_database_file(), 'eeprom_extensions.db': constants.get_eeprom_extension_database_file(), 'metrics.db': constants.get_metrics_database_file(), 'gateway.db': constants.get_gateway_database_file() }.items(): if os.path.exists(source): target = '{0}/{1}'.format(tmp_sqlite_dir, filename) backup_sqlite_db(source, target) # Backup plugins tmp_plugin_dir = '{0}/{1}'.format(tmp_dir, 'plugins') tmp_plugin_content_dir = '{0}/{1}'.format(tmp_plugin_dir, 'content') tmp_plugin_config_dir = '{0}/{1}'.format(tmp_plugin_dir, 'config') os.mkdir(tmp_plugin_dir) os.mkdir(tmp_plugin_content_dir) os.mkdir(tmp_plugin_config_dir) plugin_dir = constants.get_plugin_dir() plugins = [ name for name in os.listdir(plugin_dir) if os.path.isdir(os.path.join(plugin_dir, name)) ] for plugin in plugins: shutil.copytree( plugin_dir + plugin, '{0}/{1}/'.format(tmp_plugin_content_dir, plugin)) config_files = constants.get_plugin_configfiles() for config_file in glob.glob(config_files): shutil.copy(config_file, '{0}/'.format(tmp_plugin_config_dir)) # Backup hex files tmp_hex_dir = '{0}/{1}'.format(tmp_dir, 'hex') os.mkdir(tmp_hex_dir) hex_files = constants.get_hex_files() for hex_file in glob.glob(hex_files): shutil.copy(hex_file, '{0}/'.format(tmp_hex_dir)) # Backup general config stuff tmp_config_dir = '{0}/{1}'.format(tmp_dir, 'config') os.mkdir(tmp_config_dir) config_dir = constants.get_config_dir() for file_name in ['openmotics.conf', 'https.key', 'https.crt']: shutil.copy(os.path.join(config_dir, file_name), '{0}/'.format(tmp_config_dir)) retcode = subprocess.call( 'cd {0}; tar cf backup.tar *'.format(tmp_dir), shell=True) if retcode != 0: raise Exception('The backup tar could not be created.') with open('{0}/backup.tar'.format(tmp_dir), 'r') as backup_file: return backup_file.read() finally: shutil.rmtree(tmp_dir)
def main(): """ Main function. """ log('Starting service...') config = ConfigParser() config.read(constants.get_config_file()) defaults = {'username': config.get('OpenMotics', 'cloud_user'), 'password': config.get('OpenMotics', 'cloud_pass')} controller_serial_port = config.get('OpenMotics', 'controller_serial') passthrough_serial_port = config.get('OpenMotics', 'passthrough_serial') power_serial_port = config.get('OpenMotics', 'power_serial') gateway_uuid = config.get('OpenMotics', 'uuid') config_lock = threading.Lock() user_controller = UserController(constants.get_config_database_file(), config_lock, defaults, 3600) config_controller = ConfigurationController(constants.get_config_database_file(), config_lock) dbus_service = DBusService('openmotics_service') controller_serial = Serial(controller_serial_port, 115200) power_serial = RS485(Serial(power_serial_port, 115200, timeout=None)) master_communicator = MasterCommunicator(controller_serial) eeprom_controller = EepromController( EepromFile(master_communicator), EepromExtension(constants.get_eeprom_extension_database_file()) ) if passthrough_serial_port: passthrough_serial = Serial(passthrough_serial_port, 115200) passthrough_service = PassthroughService(master_communicator, passthrough_serial) passthrough_service.start() power_controller = PowerController(constants.get_power_database_file()) power_communicator = PowerCommunicator(power_serial, power_controller) pulse_controller = PulseCounterController( constants.get_pulse_counter_database_file(), master_communicator, eeprom_controller ) observer = Observer(master_communicator, dbus_service) gateway_api = GatewayApi(master_communicator, power_communicator, power_controller, eeprom_controller, pulse_controller, dbus_service, observer, config_controller) observer.set_gateway_api(gateway_api) scheduling_controller = SchedulingController(constants.get_scheduling_database_file(), config_lock, gateway_api) maintenance_service = MaintenanceService(gateway_api, constants.get_ssl_private_key_file(), constants.get_ssl_certificate_file()) web_interface = WebInterface(user_controller, gateway_api, maintenance_service, dbus_service, config_controller, scheduling_controller) scheduling_controller.set_webinterface(web_interface) # Plugins plugin_controller = PluginController(web_interface, config_controller) web_interface.set_plugin_controller(plugin_controller) gateway_api.set_plugin_controller(plugin_controller) # Metrics metrics_cache_controller = MetricsCacheController(constants.get_metrics_database_file(), threading.Lock()) metrics_collector = MetricsCollector(gateway_api, pulse_controller) metrics_controller = MetricsController(plugin_controller, metrics_collector, metrics_cache_controller, config_controller, gateway_uuid) metrics_collector.set_controllers(metrics_controller, plugin_controller) metrics_controller.add_receiver(metrics_controller.receiver) metrics_controller.add_receiver(web_interface.distribute_metric) plugin_controller.set_metrics_controller(metrics_controller) plugin_controller.set_metrics_collector(metrics_collector) web_interface.set_metrics_collector(metrics_collector) web_interface.set_metrics_controller(metrics_controller) web_service = WebService(web_interface, config_controller) plugin_controller.set_webservice(web_service) observer.subscribe_master(Observer.MasterEvents.INPUT_TRIGGER, metrics_collector.on_input) observer.subscribe_master(Observer.MasterEvents.INPUT_TRIGGER, plugin_controller.process_input_status) observer.subscribe_master(Observer.MasterEvents.ON_OUTPUTS, metrics_collector.on_output) observer.subscribe_master(Observer.MasterEvents.ON_OUTPUTS, plugin_controller.process_output_status) observer.subscribe_master(Observer.MasterEvents.ON_SHUTTER_UPDATE, plugin_controller.process_shutter_status) observer.subscribe_events(web_interface.process_observer_event) led_thread = threading.Thread(target=led_driver, args=(dbus_service, master_communicator, power_communicator)) led_thread.setName("Serial led driver thread") led_thread.daemon = True led_thread.start() master_communicator.start() observer.start() power_communicator.start() metrics_controller.start() scheduling_controller.start() metrics_collector.start() web_service.start() gateway_api.start() plugin_controller.start() signal_request = {'stop': False} def stop(signum, frame): """ This function is called on SIGTERM. """ _ = signum, frame log('Stopping service...') web_service.stop() metrics_collector.stop() metrics_controller.stop() plugin_controller.stop() log('Stopping service... Done') signal_request['stop'] = True signal(SIGTERM, stop) log('Starting service... Done') while not signal_request['stop']: time.sleep(1)