def test_master_event_failsafe(self): _ = self SetUpTestInjections(master_communicator=Mock(), configuration_controller=Mock(), eeprom_controller=Mock()) master_controller = MasterClassicController() master_controller._shutter_config = {shutter.id: shutter for shutter in ShutterControllerTest.SHUTTER_CONFIG} master_controller._shutter_config.pop(0) # Got data for an unconfigured shutter. This should not raise. master_controller._update_from_master_state({'module_nr': 0, 'status': 0b00000000})
def get_classic_controller_dummy(inputs=None): communicator_mock = mock.Mock(spec=MasterCommunicator) eeprom_mock = mock.Mock(EepromController) eeprom_mock.invalidate_cache.return_value = None eeprom_mock.read.return_value = inputs[0] if inputs else [] eeprom_mock.read_all.return_value = inputs SetUpTestInjections(configuration_controller=mock.Mock(), master_communicator=communicator_mock, eeprom_controller=eeprom_mock, pubsub=PubSub()) controller = MasterClassicController() controller._master_version = (3, 143, 102) return controller
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)
def get_classic_controller_dummy(inputs=None): from gateway.hal.master_controller_classic import MasterClassicController eeprom_mock = mock.Mock(EepromController) eeprom_mock.read.return_value = inputs[0] if inputs else [] eeprom_mock.read_all.return_value = inputs SetUpTestInjections(configuration_controller=mock.Mock(), master_communicator=mock.Mock(), eeprom_controller=eeprom_mock) return MasterClassicController()
def get_observer(): SetUpTestInjections(configuration_controller=mock.Mock(), eeprom_controller=mock.Mock(), master_communicator=mock.Mock()) from gateway.hal.master_controller_classic import MasterClassicController master = MasterClassicController() return Observer(master_controller=master, message_client=mock.Mock(), shutter_controller=mock.Mock())
def test_pulse_counter_status(self): data = {'pv0': 0, 'pv1': 1, 'pv2': 2, 'pv3': 3, 'pv4': 4, 'pv5': 5, 'pv6': 6, 'pv7': 7, 'pv8': 8, 'pv9': 9, 'pv10': 10, 'pv11': 11, 'pv12': 12, 'pv13': 13, 'pv14': 14, 'pv15': 15, 'pv16': 16, 'pv17': 17, 'pv18': 18, 'pv19': 19, 'pv20': 20, 'pv21': 21, 'pv22': 22, 'pv23': 23} def _do_command(api): return data master_communicator = Mock() master_communicator.do_command = _do_command SetUpTestInjections(master_communicator=master_communicator, configuration_controller=Mock(), eeprom_controller=Mock()) SetUpTestInjections(master_controller=MasterClassicController(), maintenance_controller=Mock()) for i in range(24): PulseCounter(number=i, name='PulseCounter {0}'.format(i), source='master', persistent=False).save() controller = PulseCounterController() controller.set_amount_of_pulse_counters(26) controller.set_value(24, 123) controller.set_value(25, 456) values_dict = controller.get_values() values = [values_dict[i] for i in sorted(values_dict.keys())] self.assertEqual(list(range(0, 24)) + [123, 456], values) # Set pulse counter for unexisting pulse counter with self.assertRaises(DoesNotExist): controller.set_value(26, 789) # Set pulse counter for physical pulse counter with self.assertRaises(ValueError): controller.set_value(23, 789)
def test_events_and_state(self): fakesleep.reset(0) calls = {} SetUpTestInjections(master_communicator=Mock(), configuration_controller=Mock(), eeprom_controller=Mock()) master_controller = MasterClassicController() master_controller._master_version = (3, 143, 103) master_controller._shutter_config = {shutter.id: shutter for shutter in ShutterControllerTest.SHUTTER_CONFIG} SetUpTestInjections(master_controller=master_controller, maintenance_controller=Mock()) controller = ShutterController() controller.update_config(ShutterControllerTest.SHUTTER_CONFIG) def shutter_callback(event): calls.setdefault(event.data['id'], []).append(event.data['status']['state']) self.pubsub.subscribe_gateway_events(PubSub.GatewayTopics.STATE, shutter_callback) self.pubsub._publish_all_events() def validate(_shutter_id, _entry): self.pubsub._publish_all_events() self.assertEqual(controller._actual_positions.get(_shutter_id), _entry[0]) self.assertEqual(controller._desired_positions.get(_shutter_id), _entry[1]) self.assertEqual(controller._directions.get(_shutter_id), _entry[2]) timer, state = controller._states.get(_shutter_id) self.assertEqual(timer, _entry[3][0]) self.assertEqual(state, _entry[3][1]) if len(_entry) == 4 or _entry[4]: self.assertEqual(calls[_shutter_id].pop(), _entry[3][1].upper()) master_controller._update_from_master_state({'module_nr': 0, 'status': 0b00000000}) self.pubsub._publish_all_events() for shutter_id in range(3): # +- actual position # | +- desired position # | | +- direction +- state +- optional skip call check # v v v v v validate(shutter_id, [None, None, ShutterEnums.Direction.STOP, (0.0, ShutterEnums.State.STOPPED), False]) ################################################################################################### # set stutters to a known initial state for shutter in self.SHUTTER_CONFIG: controller._directions[shutter.id] = ShutterEnums.Direction.UP controller._actual_positions[shutter.id] = 0 ################################################################################################### for shutter_id in range(3): controller.shutter_down(shutter_id, None) self.pubsub._publish_all_events() time.sleep(20) master_controller._update_from_master_state({'module_nr': 0, 'status': 0b00011001}) self.pubsub._publish_all_events() # +- actual position # | +- desired position # | | +- direction +- state # v v v v for shutter_id, entry in {0: [0, 99, ShutterEnums.Direction.DOWN, (20, ShutterEnums.State.GOING_DOWN)], 1: [0, 99, ShutterEnums.Direction.DOWN, (20, ShutterEnums.State.GOING_DOWN)], # this shutter is inverted 2: [0, 79, ShutterEnums.Direction.DOWN, (20, ShutterEnums.State.GOING_DOWN)]}.items(): validate(shutter_id, entry) self.pubsub._publish_all_events() time.sleep(50) # Standard shutters will still be going down controller._actual_positions[2] = 20 # Simulate position reporting master_controller._update_from_master_state({'module_nr': 0, 'status': 0b00011000}) # First shutter motor stop self.pubsub._publish_all_events() # +- actual position # | +- desired position # | | +- direction +- state +- optional skip call check # v v v v v for shutter_id, entry in {0: [25, 99, ShutterEnums.Direction.STOP, (70, ShutterEnums.State.STOPPED)], 1: [0, 99, ShutterEnums.Direction.DOWN, (20, ShutterEnums.State.GOING_DOWN), False], 2: [20, 79, ShutterEnums.Direction.DOWN, (20, ShutterEnums.State.GOING_DOWN), False]}.items(): validate(shutter_id, entry) self.pubsub._publish_all_events() time.sleep(50) # Standard shutters will be down now controller._actual_positions[2] = 50 # Simulate position reporting master_controller._update_from_master_state({'module_nr': 0, 'status': 0b00010000}) # Second shutter motor stop # +- actual position # | +- desired position # | | +- direction +- state +- optional skip call check # v v v v v for shutter_id, entry in {0: [25, 99, ShutterEnums.Direction.STOP, (70, ShutterEnums.State.STOPPED), False], 1: [99, 99, ShutterEnums.Direction.STOP, (120, ShutterEnums.State.DOWN)], 2: [50, 79, ShutterEnums.Direction.DOWN, (20, ShutterEnums.State.GOING_DOWN), False]}.items(): validate(shutter_id, entry) time.sleep(10) controller._actual_positions[2] = 50 # Simulate position reporting master_controller._update_from_master_state({'module_nr': 0, 'status': 0b00000000}) # Third motor stopped # +- actual position # | +- desired position # | | +- direction +- state +- optional skip call check # v v v v v for shutter_id, entry in {0: [25, 99, ShutterEnums.Direction.STOP, (70, ShutterEnums.State.STOPPED), False], 1: [99, 99, ShutterEnums.Direction.STOP, (120, ShutterEnums.State.DOWN), False], 2: [50, 79, ShutterEnums.Direction.STOP, (130, ShutterEnums.State.STOPPED)]}.items(): validate(shutter_id, entry) controller._actual_positions[2] = 60 # Simulate position reporting master_controller._update_from_master_state({'module_nr': 0, 'status': 0b00010000}) # Third motor started again # +- actual position # | +- desired position # | | +- direction +- state +- optional skip call check # v v v v v for shutter_id, entry in {0: [25, 99, ShutterEnums.Direction.STOP, (70, ShutterEnums.State.STOPPED), False], 1: [99, 99, ShutterEnums.Direction.STOP, (120, ShutterEnums.State.DOWN), False], 2: [60, 79, ShutterEnums.Direction.DOWN, (130, ShutterEnums.State.GOING_DOWN)]}.items(): validate(shutter_id, entry) controller._actual_positions[2] = 79 # Simulate position reporting master_controller._update_from_master_state({'module_nr': 0, 'status': 0b00000000}) # Third motor stopped again # +- actual position # | +- desired position # | | +- direction +- state +- optional skip call check # v v v v v for shutter_id, entry in {0: [25, 99, ShutterEnums.Direction.STOP, (70, ShutterEnums.State.STOPPED), False], 1: [99, 99, ShutterEnums.Direction.STOP, (120, ShutterEnums.State.DOWN), False], 2: [79, 79, ShutterEnums.Direction.STOP, (130, ShutterEnums.State.DOWN)]}.items(): validate(shutter_id, entry) states = controller.get_states() states['status'].pop(3) # Remove the "unused" shutter states['detail'].pop(3) self.assertDictEqual(states, {'detail': {0: {'actual_position': 25, 'desired_position': 99, 'state': 'stopped', 'last_change': 70}, 1: {'actual_position': 99, 'desired_position': 99, 'state': 'down', 'last_change': 120}, 2: {'actual_position': 79, 'desired_position': 79, 'state': 'down', 'last_change': 130}}, 'status': ['stopped', 'down', 'down']})
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())