Beispiel #1
0
    def _create_controller(get_sensor_temperature_status=None):
        gateway_api = Mock()
        gateway_api.get_timezone = lambda: 'Europe/Brussels'
        gateway_api.get_sensor_temperature_status = get_sensor_temperature_status

        SetUpTestInjections(gateway_api=gateway_api,
                            message_client=Mock(),
                            output_controller=Mock(),
                            pubsub=Mock())
        thermostat_controller = ThermostatControllerGateway()
        SetUpTestInjections(thermostat_controller=thermostat_controller)
        return thermostat_controller
 def setUp(self):
     self.test_db = SqliteDatabase(':memory:')
     self.test_db.bind(MODELS)
     self.test_db.connect()
     self.test_db.create_tables(MODELS)
     self._gateway_api = mock.Mock(GatewayApi)
     self._gateway_api.get_timezone.return_value = 'Europe/Brussels'
     self._gateway_api.get_sensor_temperature_status.return_value = 10.0
     output_controller = mock.Mock(OutputController)
     output_controller.get_output_status.return_value = OutputStateDTO(id=0, status=False)
     SetUpTestInjections(gateway_api=self._gateway_api,
                         output_controller=output_controller,
                         pubsub=mock.Mock())
     self._thermostat_controller = ThermostatControllerGateway()
     SetUpTestInjections(thermostat_controller=self._thermostat_controller)
     self._thermostat_group = ThermostatGroup.create(number=0,
                                                     name='thermostat group',
                                                     on=True,
                                                     threshold_temperature=10.0,
                                                     sensor=Sensor.create(number=1),
                                                     mode='heating')
Beispiel #3
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())
class ThermostatControllerTest(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        fakesleep.monkey_patch()
        SetTestMode()
        logger = logging.getLogger('openmotics')
        logger.setLevel(logging.DEBUG)
        logger.propagate = False
        handler = logging.StreamHandler()
        handler.setLevel(logging.DEBUG)
        handler.setFormatter(logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s"))
        logger.addHandler(handler)

    @classmethod
    def tearDownClass(cls):
        fakesleep.monkey_restore()

    def setUp(self):
        self.test_db = SqliteDatabase(':memory:')
        self.test_db.bind(MODELS)
        self.test_db.connect()
        self.test_db.create_tables(MODELS)
        self._gateway_api = mock.Mock(GatewayApi)
        self._gateway_api.get_timezone.return_value = 'Europe/Brussels'
        self._gateway_api.get_sensor_temperature_status.return_value = 10.0
        output_controller = mock.Mock(OutputController)
        output_controller.get_output_status.return_value = OutputStateDTO(id=0, status=False)
        SetUpTestInjections(gateway_api=self._gateway_api,
                            output_controller=output_controller,
                            pubsub=mock.Mock())
        self._thermostat_controller = ThermostatControllerGateway()
        SetUpTestInjections(thermostat_controller=self._thermostat_controller)
        self._thermostat_group = ThermostatGroup.create(number=0,
                                                        name='thermostat group',
                                                        on=True,
                                                        threshold_temperature=10.0,
                                                        sensor=Sensor.create(number=1),
                                                        mode='heating')

    def tearDown(self):
        self.test_db.drop_tables(MODELS)
        self.test_db.close()

    def test_save_pumpgroups(self):
        thermostat = Thermostat.create(number=1,
                                       name='thermostat 1',
                                       sensor=Sensor.create(number=10),
                                       pid_heating_p=200,
                                       pid_heating_i=100,
                                       pid_heating_d=50,
                                       pid_cooling_p=200,
                                       pid_cooling_i=100,
                                       pid_cooling_d=50,
                                       automatic=True,
                                       room=None,
                                       start=0,
                                       valve_config='equal',
                                       thermostat_group=self._thermostat_group)
        valve_1_output = Output.create(number=1)
        valve_1 = Valve.create(number=1,
                               name='valve 1',
                               output=valve_1_output)
        valve_2_output = Output.create(number=2)
        valve_2 = Valve.create(number=2,
                               name='valve 2',
                               output=valve_2_output)
        valve_3_output = Output.create(number=3)
        valve_3 = Valve.create(number=3,
                               name='valve 3',
                               output=valve_3_output)
        ValveToThermostat.create(thermostat=thermostat,
                                 valve=valve_1,
                                 mode=ThermostatGroup.Modes.HEATING,
                                 priority=0)
        ValveToThermostat.create(thermostat=thermostat,
                                 valve=valve_2,
                                 mode=ThermostatGroup.Modes.COOLING,
                                 priority=0)
        ValveToThermostat.create(thermostat=thermostat,
                                 valve=valve_3,
                                 mode=ThermostatGroup.Modes.HEATING,
                                 priority=0)
        Preset.create(type=Preset.Types.SCHEDULE,
                      heating_setpoint=20.0,
                      cooling_setpoint=25.0,
                      active=True,
                      thermostat=thermostat)
        pump_output = Output.create(number=4)
        pump = Pump.create(name='pump 1',
                           output=pump_output)

        heating_pump_groups = self._thermostat_controller.load_heating_pump_groups()
        self.assertEqual([PumpGroupDTO(id=pump.id,
                                       pump_output_id=pump_output.id,
                                       valve_output_ids=[],
                                       room_id=None)], heating_pump_groups)

        PumpToValve.create(pump=pump, valve=valve_1)
        PumpToValve.create(pump=pump, valve=valve_2)

        pump_groups = self._thermostat_controller.load_heating_pump_groups()
        self.assertEqual([PumpGroupDTO(id=pump.id,
                                       pump_output_id=pump_output.id,
                                       valve_output_ids=[valve_1_output.id],
                                       room_id=None)], pump_groups)
        pump_groups = self._thermostat_controller.load_cooling_pump_groups()
        self.assertEqual([PumpGroupDTO(id=pump.id,
                                       pump_output_id=pump_output.id,
                                       valve_output_ids=[valve_2_output.id],
                                       room_id=None)], pump_groups)

        self._thermostat_controller._save_pump_groups(ThermostatGroup.Modes.HEATING,
                                                      [(PumpGroupDTO(id=pump.id,
                                                                     pump_output_id=pump_output.id,
                                                                     valve_output_ids=[valve_1_output.id, valve_3_output.id]),
                                                        ['pump_output_id', 'valve_output_ids'])])
        pump_groups = self._thermostat_controller.load_heating_pump_groups()
        self.assertEqual([PumpGroupDTO(id=pump.id,
                                       pump_output_id=pump_output.id,
                                       valve_output_ids=[valve_1_output.id, valve_3_output.id],
                                       room_id=None)], pump_groups)
        pump_groups = self._thermostat_controller.load_cooling_pump_groups()
        self.assertEqual([PumpGroupDTO(id=pump.id,
                                       pump_output_id=pump_output.id,
                                       valve_output_ids=[valve_2_output.id],
                                       room_id=None)], pump_groups)

    def test_thermostat_group_crud(self):
        thermostat = Thermostat.create(number=1,
                                       name='thermostat 1',
                                       sensor=Sensor.create(number=10),
                                       pid_heating_p=200,
                                       pid_heating_i=100,
                                       pid_heating_d=50,
                                       pid_cooling_p=200,
                                       pid_cooling_i=100,
                                       pid_cooling_d=50,
                                       automatic=True,
                                       room=None,
                                       start=0,
                                       valve_config='equal',
                                       thermostat_group=self._thermostat_group)
        Output.create(number=1)
        Output.create(number=2)
        Output.create(number=3)
        valve_output = Output.create(number=4)
        valve = Valve.create(number=1,
                             name='valve 1',
                             output=valve_output)
        ValveToThermostat.create(thermostat=thermostat,
                                 valve=valve,
                                 mode=ThermostatGroup.Modes.HEATING,
                                 priority=0)
        thermostat_group = ThermostatGroup.get(number=0)  # type: ThermostatGroup
        self.assertEqual(10.0, thermostat_group.threshold_temperature)
        self.assertEqual(0, OutputToThermostatGroup.select()
                                                   .where(OutputToThermostatGroup.thermostat_group == thermostat_group)
                                                   .count())
        self._thermostat_controller.save_thermostat_group((ThermostatGroupDTO(id=0,
                                                                              outside_sensor_id=1,
                                                                              pump_delay=30,
                                                                              threshold_temperature=15,
                                                                              switch_to_heating_0=(1, 0),
                                                                              switch_to_heating_1=(2, 100),
                                                                              switch_to_cooling_0=(1, 100)),
                                                           ['outside_sensor_id', 'pump_delay', 'threshold_temperature',
                                                            'switch_to_heating_0', 'switch_to_heating_1',
                                                            'switch_to_cooling_0']))
        thermostat_group = ThermostatGroup.get(number=0)
        self.assertEqual(15.0, thermostat_group.threshold_temperature)
        links = [{'index': link.index, 'value': link.value, 'mode': link.mode, 'output': link.output_id}
                 for link in (OutputToThermostatGroup.select()
                                                     .where(OutputToThermostatGroup.thermostat_group == thermostat_group))]
        self.assertEqual(3, len(links))
        self.assertIn({'index': 0, 'value': 0, 'mode': 'heating', 'output': 1}, links)
        self.assertIn({'index': 1, 'value': 100, 'mode': 'heating', 'output': 2}, links)
        self.assertIn({'index': 0, 'value': 100, 'mode': 'cooling', 'output': 1}, links)

        new_thermostat_group_dto = ThermostatGroupDTO(id=0,
                                                      outside_sensor_id=1,
                                                      pump_delay=60,
                                                      threshold_temperature=10,
                                                      switch_to_heating_0=(1, 50),
                                                      switch_to_cooling_0=(2, 0))
        self._thermostat_controller.save_thermostat_group((new_thermostat_group_dto,
                                                           ['outside_sensor_id', 'pump_delay', 'threshold_temperature',
                                                            'switch_to_heating_0', 'switch_to_heating_1', 'switch_to_cooling_0']))
        thermostat_group = ThermostatGroup.get(number=0)
        self.assertEqual(10.0, thermostat_group.threshold_temperature)
        links = [{'index': link.index, 'value': link.value, 'mode': link.mode, 'output': link.output_id}
                 for link in (OutputToThermostatGroup.select()
                                                     .where(OutputToThermostatGroup.thermostat_group == thermostat_group))]
        self.assertEqual(2, len(links))
        self.assertIn({'index': 0, 'value': 50, 'mode': 'heating', 'output': 1}, links)
        self.assertIn({'index': 0, 'value': 0, 'mode': 'cooling', 'output': 2}, links)

        self.assertEqual(new_thermostat_group_dto, self._thermostat_controller.load_thermostat_group())

    def test_thermostat_control(self):
        thermostat = Thermostat.create(number=1,
                                       name='thermostat 1',
                                       sensor=Sensor.create(number=10),
                                       pid_heating_p=200,
                                       pid_heating_i=100,
                                       pid_heating_d=50,
                                       pid_cooling_p=200,
                                       pid_cooling_i=100,
                                       pid_cooling_d=50,
                                       automatic=True,
                                       room=None,
                                       start=0,
                                       valve_config='equal',
                                       thermostat_group=self._thermostat_group)
        Output.create(number=1)
        Output.create(number=2)
        Output.create(number=3)
        valve_output = Output.create(number=4)
        valve = Valve.create(number=1,
                             name='valve 1',
                             output=valve_output)
        ValveToThermostat.create(thermostat=thermostat,
                                 valve=valve,
                                 mode=ThermostatGroup.Modes.HEATING,
                                 priority=0)
        self._thermostat_controller.refresh_config_from_db()

        expected = ThermostatGroupStatusDTO(id=0,
                                            on=True,
                                            setpoint=0,
                                            cooling=False,
                                            automatic=True,
                                            statusses=[ThermostatStatusDTO(id=1,
                                                                           name='thermostat 1',
                                                                           automatic=True,
                                                                           setpoint=0,
                                                                           sensor_id=10,
                                                                           actual_temperature=10.0,
                                                                           setpoint_temperature=14.0,
                                                                           outside_temperature=10.0,
                                                                           output_0_level=0,
                                                                           output_1_level=0,
                                                                           mode=0,
                                                                           airco=0)])
        self.assertEqual(expected, self._thermostat_controller.get_thermostat_status())

        self._thermostat_controller.set_current_setpoint(thermostat_number=1, heating_temperature=15.0)
        expected.statusses[0].setpoint_temperature = 15.0
        self.assertEqual(expected, self._thermostat_controller.get_thermostat_status())

        self._thermostat_controller.set_per_thermostat_mode(thermostat_number=1,
                                                            automatic=True,
                                                            setpoint=16.0)
        expected.statusses[0].setpoint_temperature = 16.0
        self.assertEqual(expected, self._thermostat_controller.get_thermostat_status())

        preset = self._thermostat_controller.get_current_preset(thermostat_number=1)
        self.assertTrue(preset.active)
        self.assertEqual(30.0, preset.cooling_setpoint)
        self.assertEqual(16.0, preset.heating_setpoint)
        self.assertEqual(Preset.Types.SCHEDULE, preset.type)

        self._thermostat_controller.set_current_preset(thermostat_number=1, preset_type=Preset.Types.PARTY)
        expected.statusses[0].setpoint_temperature = 14.0
        expected.statusses[0].setpoint = expected.setpoint = 5  # PARTY = legacy `5` setpoint
        expected.statusses[0].automatic = expected.automatic = False
        self.assertEqual(expected, self._thermostat_controller.get_thermostat_status())

        self._thermostat_controller.set_thermostat_mode(thermostat_on=True, cooling_mode=True, cooling_on=True, automatic=False, setpoint=4)
        expected.statusses[0].setpoint_temperature = 30.0
        expected.statusses[0].setpoint = expected.setpoint = 4  # VACATION = legacy `4` setpoint
        expected.cooling = True
        self.assertEqual(expected, self._thermostat_controller.get_thermostat_status())

        self._thermostat_controller.set_thermostat_mode(thermostat_on=True, cooling_mode=False, cooling_on=True, automatic=True)
        expected.statusses[0].setpoint_temperature = 16.0
        expected.statusses[0].setpoint = expected.setpoint = 0  # AUTO = legacy `0/1/2` setpoint
        expected.statusses[0].automatic = expected.automatic = True
        expected.cooling = False
        self.assertEqual(expected, self._thermostat_controller.get_thermostat_status())