def main():
    """ Main function. """
    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)

    led_service = LedService()

    controller_serial = Serial(controller_serial_port, 115200)
    power_serial = RS485(Serial(power_serial_port, 115200, timeout=None))

    master_communicator = MasterCommunicator(controller_serial)

    if passthrough_serial_port:
        passthrough_serial = Serial(passthrough_serial_port, 115200)
        passthrough_service = PassthroughService(master_communicator,
                                                 passthrough_serial)
        passthrough_service.start()

    master_communicator.start(
    )  # A running master_communicator is required for the startup of services below

    power_controller = PowerController(constants.get_power_database_file())
    power_communicator = PowerCommunicator(power_serial, power_controller)

    gateway_api = GatewayApi(master_communicator, power_communicator,
                             power_controller)

    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,
                                 led_service.in_authorized_mode,
                                 config_controller, scheduling_controller)

    scheduling_controller.set_webinterface(web_interface)

    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)
    metrics_controller = MetricsController(plugin_controller,
                                           metrics_collector,
                                           metrics_cache_controller,
                                           config_controller, gateway_uuid)
    metrics_collector.set_controllers(metrics_controller, plugin_controller)
    metrics_collector.set_plugin_intervals(plugin_controller.metric_intervals)
    metrics_controller.add_receiver(metrics_controller.receiver)
    metrics_controller.add_receiver(web_interface.distribute_metric)

    plugin_controller.set_metrics_controller(metrics_controller)
    web_interface.set_metrics_collector(metrics_collector)
    web_interface.set_metrics_controller(metrics_controller)

    web_service = WebService(web_interface, config_controller)

    def _on_output(*args, **kwargs):
        metrics_collector.on_output(*args, **kwargs)
        gateway_api.on_outputs(*args, **kwargs)

    def _on_input(*args, **kwargs):
        metrics_collector.on_input(*args, **kwargs)
        gateway_api.on_inputs(*args, **kwargs)

    master_communicator.register_consumer(
        BackgroundConsumer(master_api.output_list(), 0, _on_output, True))
    master_communicator.register_consumer(
        BackgroundConsumer(master_api.input_list(), 0, _on_input))

    power_communicator.start()
    plugin_controller.start_plugins()
    metrics_controller.start()
    scheduling_controller.start()
    metrics_collector.start()
    web_service.start()

    led_thread = threading.Thread(target=led_driver,
                                  args=(led_service, master_communicator,
                                        power_communicator))
    led_thread.setName("Serial led driver thread")
    led_thread.daemon = True
    led_thread.start()

    def stop(signum, frame):
        """ This function is called on SIGTERM. """
        _ = signum, frame
        sys.stderr.write("Shutting down")
        web_service.stop()
        metrics_collector.stop()
        metrics_controller.stop()
        plugin_controller.stop()

    signal(SIGTERM, stop)
Beispiel #2
0
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())
Beispiel #4
0
    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()
Beispiel #5
0
    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)