def setUp(self): self.pubsub = PubSub() SetUpTestInjections(pubsub=self.pubsub) self.power_data = [] # type: list SetUpTestInjections(power_db=':memory:') self.serial = RS485(SerialMock(self.power_data)) self.store = PowerStore() SetUpTestInjections(power_serial=self.serial, power_store=self.store) self.communicator = PowerCommunicator()
def setup_minimal_power_platform(): # type: () -> None config = ConfigParser() config.read(constants.get_config_file()) power_serial_port = config.get('OpenMotics', 'power_serial') if power_serial_port: Injectable.value(power_db=constants.get_power_database_file()) Injectable.value(power_store=PowerStore()) 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_store=None) Injectable.value(power_communicator=None) Injectable.value(power_controller=None) Injectable.value(p1_controller=None) Injectable.value(power_serial=None)
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 setUp(self): self.power_communicator = mock.Mock() SetUpTestInjections(power_communicator=self.power_communicator, power_db=':memory:') self.store = PowerStore()
class PowerControllerTest(unittest.TestCase): """ Tests for PowerStore. """ @classmethod def setUpClass(cls): SetTestMode() def setUp(self): self.power_communicator = mock.Mock() SetUpTestInjections(power_communicator=self.power_communicator, power_db=':memory:') self.store = PowerStore() def test_empty(self): """ Test an empty database. """ self.assertEqual({}, self.store.get_power_modules()) self.assertEqual(1, self.store.get_free_address()) self.store.register_power_module(1, POWER_MODULE) self.assertEqual( { 1: { 'id': 1, 'address': 1, 'name': u'', 'version': 8, 'input0': u'', 'input1': u'', 'input2': u'', 'input3': u'', 'input4': u'', 'input5': u'', 'input6': u'', 'input7': u'', 'sensor0': 0, 'sensor1': 0, 'sensor2': 0, 'sensor3': 0, 'sensor4': 0, 'sensor5': 0, 'sensor6': 0, 'sensor7': 0, 'times0': None, 'times1': None, 'times2': None, 'times3': None, 'times4': None, 'times5': None, 'times6': None, 'times7': None, 'inverted0': 0, 'inverted1': 0, 'inverted2': 0, 'inverted3': 0, 'inverted4': 0, 'inverted5': 0, 'inverted6': 0, 'inverted7': 0 } }, self.store.get_power_modules()) self.assertEqual(2, self.store.get_free_address()) self.store.register_power_module(5, POWER_MODULE) self.assertEqual( { 1: { 'id': 1, 'address': 1, 'name': u'', 'version': 8, 'input0': u'', 'input1': u'', 'input2': u'', 'input3': u'', 'input4': u'', 'input5': u'', 'input6': u'', 'input7': u'', 'sensor0': 0, 'sensor1': 0, 'sensor2': 0, 'sensor3': 0, 'sensor4': 0, 'sensor5': 0, 'sensor6': 0, 'sensor7': 0, 'times0': None, 'times1': None, 'times2': None, 'times3': None, 'times4': None, 'times5': None, 'times6': None, 'times7': None, 'inverted0': 0, 'inverted1': 0, 'inverted2': 0, 'inverted3': 0, 'inverted4': 0, 'inverted5': 0, 'inverted6': 0, 'inverted7': 0 }, 2: { 'id': 2, 'address': 5, 'name': u'', 'version': 8, 'input0': u'', 'input1': u'', 'input2': u'', 'input3': u'', 'input4': u'', 'input5': u'', 'input6': u'', 'input7': u'', 'sensor0': 0, 'sensor1': 0, 'sensor2': 0, 'sensor3': 0, 'sensor4': 0, 'sensor5': 0, 'sensor6': 0, 'sensor7': 0, 'times0': None, 'times1': None, 'times2': None, 'times3': None, 'times4': None, 'times5': None, 'times6': None, 'times7': None, 'inverted0': 0, 'inverted1': 0, 'inverted2': 0, 'inverted3': 0, 'inverted4': 0, 'inverted5': 0, 'inverted6': 0, 'inverted7': 0 } }, self.store.get_power_modules()) self.assertEqual(6, self.store.get_free_address()) def test_update(self): """ Test for updating the power module information. """ self.assertEqual({}, self.store.get_power_modules()) self.store.register_power_module(1, POWER_MODULE) self.assertEqual( { 1: { 'id': 1, 'address': 1, 'name': u'', 'version': 8, 'input0': u'', 'input1': u'', 'input2': u'', 'input3': u'', 'input4': u'', 'input5': u'', 'input6': u'', 'input7': u'', 'sensor0': 0, 'sensor1': 0, 'sensor2': 0, 'sensor3': 0, 'sensor4': 0, 'sensor5': 0, 'sensor6': 0, 'sensor7': 0, 'times0': None, 'times1': None, 'times2': None, 'times3': None, 'times4': None, 'times5': None, 'times6': None, 'times7': None, 'inverted0': 0, 'inverted1': 0, 'inverted2': 0, 'inverted3': 0, 'inverted4': 0, 'inverted5': 0, 'inverted6': 0, 'inverted7': 0 } }, self.store.get_power_modules()) times = ",".join(["00:00" for _ in range(14)]) self.store.update_power_module({ 'id': 1, 'name': 'module1', 'input0': 'in0', 'input1': 'in1', 'input2': 'in2', 'input3': 'in3', 'input4': 'in4', 'input5': 'in5', 'input6': 'in6', 'input7': 'in7', 'sensor0': 0, 'sensor1': 1, 'sensor2': 2, 'sensor3': 3, 'sensor4': 4, 'sensor5': 5, 'sensor6': 6, 'sensor7': 7, 'times0': times, 'times1': times, 'times2': times, 'times3': times, 'times4': times, 'times5': times, 'times6': times, 'times7': times, 'inverted0': 0, 'inverted1': 0, 'inverted2': 0, 'inverted3': 0, 'inverted4': 0, 'inverted5': 0, 'inverted6': 0, 'inverted7': 0 }) self.assertEqual( { 1: { 'id': 1, 'address': 1, 'version': 8, 'name': 'module1', 'input0': 'in0', 'input1': 'in1', 'input2': 'in2', 'input3': 'in3', 'input4': 'in4', 'input5': 'in5', 'input6': 'in6', 'input7': 'in7', 'sensor0': 0, 'sensor1': 1, 'sensor2': 2, 'sensor3': 3, 'sensor4': 4, 'sensor5': 5, 'sensor6': 6, 'sensor7': 7, 'times0': times, 'times1': times, 'times2': times, 'times3': times, 'times4': times, 'times5': times, 'times6': times, 'times7': times, 'inverted0': 0, 'inverted1': 0, 'inverted2': 0, 'inverted3': 0, 'inverted4': 0, 'inverted5': 0, 'inverted6': 0, 'inverted7': 0 } }, self.store.get_power_modules()) def test_module_exists(self): """ Test for module_exists. """ self.assertFalse(self.store.module_exists(1)) self.store.register_power_module(1, POWER_MODULE) self.assertTrue(self.store.module_exists(1)) self.assertFalse(self.store.module_exists(2)) def test_readdress_power_module(self): """ Test for readdress_power_module. """ self.store.register_power_module(1, POWER_MODULE) self.store.readdress_power_module(1, 2) self.assertFalse(self.store.module_exists(1)) self.assertTrue(self.store.module_exists(2)) self.assertEqual( { 1: { 'id': 1, 'address': 2, 'name': u'', 'version': 8, 'input0': u'', 'input1': u'', 'input2': u'', 'input3': u'', 'input4': u'', 'input5': u'', 'input6': u'', 'input7': u'', 'sensor0': 0, 'sensor1': 0, 'sensor2': 0, 'sensor3': 0, 'sensor4': 0, 'sensor5': 0, 'sensor6': 0, 'sensor7': 0, 'times0': None, 'times1': None, 'times2': None, 'times3': None, 'times4': None, 'times5': None, 'times6': None, 'times7': None, 'inverted0': 0, 'inverted1': 0, 'inverted2': 0, 'inverted3': 0, 'inverted4': 0, 'inverted5': 0, 'inverted6': 0, 'inverted7': 0 } }, self.store.get_power_modules()) def test_get_address(self): """ Test for get_address. """ self.assertEqual({}, self.store.get_power_modules()) self.store.register_power_module(1, POWER_MODULE) self.store.readdress_power_module(1, 3) self.assertEqual(3, self.store.get_address(1))
class PowerCommunicatorTest(unittest.TestCase): """ Tests for PowerCommunicator class """ @classmethod def setUpClass(cls): SetTestMode() def setUp(self): self.pubsub = PubSub() SetUpTestInjections(pubsub=self.pubsub) self.power_data = [] # type: list SetUpTestInjections(power_db=':memory:') self.serial = RS485(SerialMock(self.power_data)) self.store = PowerStore() SetUpTestInjections(power_serial=self.serial, power_store=self.store) self.communicator = PowerCommunicator() def tearDown(self): self.communicator.stop() self.serial.stop() def test_do_command(self): """ Test for standard behavior PowerCommunicator.do_command. """ action = power_api.get_voltage(power_api.POWER_MODULE) self.power_data.extend([ sin(action.create_input(1, 1)), sout(action.create_output(1, 1, 49.5)) ]) self.serial.start() self.communicator.start() output = self.communicator.do_command(1, action) self.assertEqual((49.5, ), output) self.assertEqual(14, self.communicator.get_communication_statistics()['bytes_written']) self.assertEqual(18, self.communicator.get_communication_statistics()['bytes_read']) def test_do_command_timeout_once(self): """ Test for timeout in PowerCommunicator.do_command. """ action = power_api.get_voltage(power_api.POWER_MODULE) self.power_data.extend([ sin(action.create_input(1, 1)), sout(bytearray()), sin(action.create_input(1, 2)), sout(action.create_output(1, 2, 49.5)) ]) self.serial.start() self.communicator.start() output = self.communicator.do_command(1, action) self.assertEqual((49.5, ), output) def test_do_command_timeout_twice(self): """ Test for timeout in PowerCommunicator.do_command. """ action = power_api.get_voltage(power_api.POWER_MODULE) self.power_data.extend([ sin(action.create_input(1, 1)), sout(bytearray()), sin(action.create_input(1, 2)), sout(bytearray()) ]) self.serial.start() self.communicator.start() with self.assertRaises(CommunicationTimedOutException): self.communicator.do_command(1, action) def test_do_command_split_data(self): """ Test PowerCommunicator.do_command when the data is split over multiple reads. """ action = power_api.get_voltage(power_api.POWER_MODULE) out = action.create_output(1, 1, 49.5) self.power_data.extend([ sin(action.create_input(1, 1)), sout(out[:5]), sout(out[5:]) ]) self.serial.start() self.communicator.start() output = self.communicator.do_command(1, action) self.assertEqual((49.5, ), output) def test_wrong_response(self): """ Test PowerCommunicator.do_command when the power module returns a wrong response. """ action_1 = power_api.get_voltage(power_api.POWER_MODULE) action_2 = power_api.get_frequency(power_api.POWER_MODULE) self.power_data.extend([ sin(action_1.create_input(1, 1)), sout(action_2.create_output(3, 2, 49.5)) ]) self.serial.start() self.communicator.start() with self.assertRaises(Exception): self.communicator.do_command(1, action_1) @mark.slow def test_address_mode(self): """ Test the address mode. """ events = [] def handle_events(master_event): events.append(master_event) self.pubsub.subscribe_master_events(PubSub.MasterTopics.POWER, handle_events) sad = power_api.set_addressmode(power_api.POWER_MODULE) sad_p1c = power_api.set_addressmode(power_api.P1_CONCENTRATOR) self.power_data.extend([ sin(sad.create_input(power_api.BROADCAST_ADDRESS, 1, power_api.ADDRESS_MODE)), sin(sad_p1c.create_input(power_api.BROADCAST_ADDRESS, 2, power_api.ADDRESS_MODE)), sout(power_api.want_an_address(power_api.POWER_MODULE).create_output(0, 0)), sin(power_api.set_address(power_api.POWER_MODULE).create_input(0, 0, 1)), sout(power_api.want_an_address(power_api.ENERGY_MODULE).create_output(0, 0)), sin(power_api.set_address(power_api.ENERGY_MODULE).create_input(0, 0, 2)), sout(power_api.want_an_address(power_api.P1_CONCENTRATOR).create_output(0, 0)), sin(power_api.set_address(power_api.P1_CONCENTRATOR).create_input(0, 0, 3)), sout(bytearray()), # Timeout read after 1 second sin(sad.create_input(power_api.BROADCAST_ADDRESS, 3, power_api.NORMAL_MODE)), sin(sad_p1c.create_input(power_api.BROADCAST_ADDRESS, 4, power_api.NORMAL_MODE)) ]) self.serial.start() self.communicator.start() self.assertEqual(self.store.get_free_address(), 1) self.communicator.start_address_mode() self.assertTrue(self.communicator.in_address_mode()) self.pubsub._publish_all_events() time.sleep(0.5) assert [] == events self.communicator.stop_address_mode() self.pubsub._publish_all_events() assert MasterEvent(MasterEvent.Types.POWER_ADDRESS_EXIT, {}) in events assert len(events) == 1 self.assertEqual(self.store.get_free_address(), 4) self.assertFalse(self.communicator.in_address_mode()) @mark.slow def test_do_command_in_address_mode(self): """ Test the behavior of do_command in address mode.""" action = power_api.get_voltage(power_api.POWER_MODULE) sad = power_api.set_addressmode(power_api.POWER_MODULE) sad_p1c = power_api.set_addressmode(power_api.P1_CONCENTRATOR) self.power_data.extend([ sin(sad.create_input(power_api.BROADCAST_ADDRESS, 1, power_api.ADDRESS_MODE)), sin(sad_p1c.create_input(power_api.BROADCAST_ADDRESS, 2, power_api.ADDRESS_MODE)), sout(bytearray()), # Timeout read after 1 second sin(sad.create_input(power_api.BROADCAST_ADDRESS, 3, power_api.NORMAL_MODE)), sin(sad_p1c.create_input(power_api.BROADCAST_ADDRESS, 4, power_api.NORMAL_MODE)), sin(action.create_input(1, 5)), sout(action.create_output(1, 5, 49.5)) ]) self.serial.start() self.communicator.start() self.communicator.start_address_mode() with self.assertRaises(InAddressModeException): self.communicator.do_command(1, action) self.communicator.stop_address_mode() self.assertEqual((49.5, ), self.communicator.do_command(1, action)) @mark.slow def test_address_mode_timeout(self): """ Test address mode timeout. """ action = power_api.get_voltage(power_api.POWER_MODULE) sad = power_api.set_addressmode(power_api.POWER_MODULE) sad_p1c = power_api.set_addressmode(power_api.P1_CONCENTRATOR) self.power_data.extend([ sin(sad.create_input(power_api.BROADCAST_ADDRESS, 1, power_api.ADDRESS_MODE)), sin(sad_p1c.create_input(power_api.BROADCAST_ADDRESS, 2, power_api.ADDRESS_MODE)), sout(bytearray()), # Timeout read after 1 second sin(sad.create_input(power_api.BROADCAST_ADDRESS, 3, power_api.NORMAL_MODE)), sin(sad_p1c.create_input(power_api.BROADCAST_ADDRESS, 4, power_api.NORMAL_MODE)), sin(action.create_input(1, 5)), sout(action.create_output(1, 5, 49.5)) ]) self.communicator = PowerCommunicator(address_mode_timeout=1) self.serial.start() self.communicator.start() self.communicator.start_address_mode() time.sleep(1.1) self.assertEqual((49.5, ), self.communicator.do_command(1, action)) @mark.slow def test_timekeeper(self): """ Test the TimeKeeper. """ self.store.register_power_module(1, power_api.POWER_MODULE) time_action = power_api.set_day_night(power_api.POWER_MODULE) times = [power_api.NIGHT for _ in range(8)] action = power_api.get_voltage(power_api.POWER_MODULE) self.power_data.extend([ sin(time_action.create_input(1, 1, *times)), sout(time_action.create_output(1, 1)), sin(action.create_input(1, 2)), sout(action.create_output(1, 2, 243)) ]) self.communicator = PowerCommunicator(time_keeper_period=1) self.serial.start() self.communicator.start() time.sleep(1.5) self.assertEqual((243, ), self.communicator.do_command(1, action))