def test_split_data(self): action = master_api.basic_action() fields = {'action_type': 1, 'action_number': 2} sequence = [] for i in range(1, 18): sequence.append(action.create_input(i, fields)) pty = DummyPty(sequence) SetUpTestInjections(controller_serial=pty) comm = MasterCommunicator(init_master=False) comm.start() for i in range(1, 18): data = action.create_output(i, {'resp': 'OK'}) ready = threading.Event() def write(): pty.master_reply(data[:i]) ready.set() pty.master_wait() pty.fd.write(data[i:]) thread = threading.Thread(target=write) thread.start() ready.wait(2) output = comm.do_command(action, fields) self.assertEqual('OK', output['resp']) thread.join(2) assert not thread.is_alive()
def test_background_consumer_passthrough(self): action = master_api.basic_action() fields = {'action_type': 1, 'action_number': 2} pty = DummyPty([action.create_input(1, fields)]) SetUpTestInjections(controller_serial=pty) got_output = {'passed': False} def callback(output_): """ Callback that check if the correct result was returned for OL. """ self.assertEqual([(3, int(12 * 10.0 / 6.0))], output_['outputs']) got_output['passed'] = True consumer = BackgroundConsumer(master_api.output_list(), 0, callback, True) comm = MasterCommunicator(init_master=False) comm.enable_passthrough() comm.register_consumer(consumer) comm.start() pty.fd.write(bytearray(b'OL\x00\x01')) pty.fd.write(bytearray(b'\x03\x0c\r\n')) pty.master_reply(action.create_output(1, {'resp': 'OK'})) output = comm.do_command(action, fields) self.assertEqual('OK', output['resp']) try: consumer._consume() except: pass # Just ensure it has at least consumed once self.assertEqual(True, got_output['passed']) self.assertEqual(bytearray(b'OL\x00\x01\x03\x0c\r\n'), comm.get_passthrough_data())
def test_background_consumer(self): action = master_api.basic_action() fields = {'action_type': 1, 'action_number': 2} pty = DummyPty([action.create_input(1, fields)]) SetUpTestInjections(controller_serial=pty) got_output = {'phase': 1} def callback(output): """ Callback that check if the correct result was returned for OL. """ if got_output['phase'] == 1: self.assertEqual([(3, int(12 * 10.0 / 6.0))], output['outputs']) got_output['phase'] = 2 elif got_output['phase'] == 2: self.assertEqual([(3, int(12 * 10.0 / 6.0)), (5, int(6 * 10.0 / 6.0))], output['outputs']) got_output['phase'] = 3 comm = MasterCommunicator(init_master=False) comm.enable_passthrough() comm.register_consumer(BackgroundConsumer(master_api.output_list(), 0, callback)) comm.start() pty.fd.write(bytearray(b'OL\x00\x01\x03\x0c\r\n')) pty.fd.write(bytearray(b'junkOL\x00\x02\x03\x0c\x05\x06\r\n here')) pty.master_reply(action.create_output(1, {'resp': 'OK'})) output = comm.do_command(action, fields) self.assertEqual('OK', output['resp']) time.sleep(0.2) self.assertEqual(3, got_output['phase']) self.assertEqual(bytearray(b'junk here'), comm.get_passthrough_data())
def test_timeout(self): action = master_api.basic_action() fields = {'action_type': 1, 'action_number': 2} pty = DummyPty([action.create_input(1, fields)]) SetUpTestInjections(controller_serial=pty) comm = MasterCommunicator(init_master=False) comm.start() self.assertRaises(CommunicationTimedOutException, comm.do_command, action, fields)
def test_do_command(self): action = master_api.basic_action() fields = {'action_type': 1, 'action_number': 2} pty = DummyPty([action.create_input(1, fields)]) SetUpTestInjections(controller_serial=pty) comm = MasterCommunicator(init_master=False) comm.start() pty.master_reply(action.create_output(1, {'resp': 'OK'})) output = comm.do_command(action, fields) self.assertEqual('OK', output['resp'])
def test_passthrough(self): pty = DummyPty([b'data from passthrough']) SetUpTestInjections(controller_serial=pty) comm = MasterCommunicator(init_master=False) comm.enable_passthrough() comm.start() pty.master_reply(bytearray(b'got it!')) comm.send_passthrough_data(bytearray(b'data from passthrough')) self.assertEqual(bytearray(b'got it!'), comm.get_passthrough_data())
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 test_timeout_ongoing(self): action = master_api.basic_action() fields = {'action_type': 1, 'action_number': 2} pty = DummyPty([action.create_input(1, fields), action.create_input(2, fields)]) SetUpTestInjections(controller_serial=pty) comm = MasterCommunicator(init_master=False) comm.start() self.assertRaises(CommunicationTimedOutException, comm.do_command, action, fields, timeout=0.1) pty.master_reply(action.create_output(2, {'resp': 'OK'})) output = comm.do_command(action, fields) self.assertEqual('OK', output['resp'])
def test_crc_checking(self): action = master_api.sensor_humidity_list() fields1 = {} for i in range(0, 32): fields1['hum%d' % i] = master_api.Svt(master_api.Svt.RAW, i) fields1['crc'] = bytearray([ord('C'), 1, 240]) fields2 = {} for i in range(0, 32): fields2['hum%d' % i] = master_api.Svt(master_api.Svt.RAW, 2 * i) fields2['crc'] = bytearray([ord('C'), 0, 0]) pty = DummyPty([action.create_input(1), action.create_input(2)]) SetUpTestInjections(controller_serial=pty) comm = MasterCommunicator(init_master=False) comm.start() pty.master_reply(action.create_output(1, fields1)) output = comm.do_command(action) self.assertEqual(bytearray(b'\x00'), output['hum0'].get_byte()) self.assertEqual(bytearray(b'\x01'), output['hum1'].get_byte()) pty.master_reply(action.create_output(2, fields2)) with self.assertRaises(CrcCheckFailedException): comm.do_command(action)
def test_passthrough(self): """ Test the passthrough. """ master_pty = DummyPty( [bytearray(b'response'), bytearray(b'more response')]) passthrough_mock = SerialMock([ sin(bytearray(b'data for the passthrough')), sout(bytearray(b'response')), sin(bytearray(b'more data')), sout(bytearray(b'more response')) ]) SetUpTestInjections(controller_serial=master_pty, passthrough_serial=passthrough_mock) master_communicator = MasterCommunicator(init_master=False) master_communicator.enable_passthrough() master_communicator.start() SetUpTestInjections(master_communicator=master_communicator) passthrough = PassthroughService() passthrough.start() master_pty.fd.write(bytearray(b'data for the passthrough')) master_pty.master_wait() master_pty.fd.write(bytearray(b'more data')) master_pty.master_wait() time.sleep(0.2) self.assertEqual( 33, master_communicator.get_communication_statistics()['bytes_read']) self.assertEqual( 21, master_communicator.get_communication_statistics() ['bytes_written']) self.assertEqual(21, passthrough_mock.bytes_read) self.assertEqual(33, passthrough_mock.bytes_written) passthrough.stop()
def test_misalignment(self): action = master_api.basic_action() fields = {'action_type': 1, 'action_number': 2} pty = DummyPty([action.create_input(1, fields), action.create_input(2, fields)]) SetUpTestInjections(controller_serial=pty) comm = MasterCommunicator(init_master=False) comm.start() data = action.create_output(1, {'resp': 'OK'}) # Only send partial data. pty.master_reply(data[:-4]) pty.master_reply(b'\r\n') with self.assertRaises(CommunicationTimedOutException): comm.do_command(action, fields) # Next message processed correctly. pty.master_reply(action.create_output(2, {'resp': 'OK'})) output = comm.do_command(action, fields) self.assertEqual('OK', output['resp'])
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 test_bytes_counter(self): action = master_api.basic_action() fields = {'action_type': 1, 'action_number': 2} pty = DummyPty([action.create_input(1, fields)]) SetUpTestInjections(controller_serial=pty) comm = MasterCommunicator(init_master=False) comm.enable_passthrough() comm.start() pty.fd.write(bytearray(b'hello')) pty.master_reply(action.create_output(1, {'resp': 'OK'})) comm.do_command(action, fields) self.assertEqual(bytearray(b'hello'), comm.get_passthrough_data()) self.assertEqual(21, comm.get_communication_statistics()['bytes_written']) self.assertEqual(5 + 18, comm.get_communication_statistics()['bytes_read'])
def test_maintenance_passthrough(self): action = master_api.basic_action() fields = {'action_type': 1, 'action_number': 2} pty = DummyPty([master_api.to_cli_mode().create_input(0), bytearray(b'error list\r\n'), bytearray(b'exit\r\n')]) SetUpTestInjections(controller_serial=pty) comm = MasterCommunicator(init_master=False) comm.enable_passthrough() comm.start() ready = threading.Event() def get_passthrough(): """ Background thread that reads the passthrough data. """ self.assertEqual('Before maintenance', comm.get_passthrough_data()) ready.set() self.assertEqual('After maintenance', comm.get_passthrough_data()) thread = threading.Thread(target=get_passthrough) thread.start() pty.fd.write(bytearray(b'Before maintenance')) ready.wait(2) comm.start_maintenance_mode() pty.fd.write(bytearray(b'OK')) time.sleep(0.2) self.assertRaises(InMaintenanceModeException, comm.do_command, action, fields) self.assertEqual(bytearray(b'OK'), comm.get_maintenance_data()) comm.send_maintenance_data(bytearray(b'error list\r\n')) pty.fd.write(bytearray(b'the list\n')) time.sleep(0.2) self.assertEqual(bytearray(b'the list\n'), comm.get_maintenance_data()) comm.stop_maintenance_mode() pty.fd.write(bytearray(b'After maintenance')) thread.join(2) assert not thread.is_alive()
def test_maintenance_mode(self): action = master_api.basic_action() fields = {'action_type': 1, 'action_number': 2} pty = DummyPty([master_api.to_cli_mode().create_input(0), bytearray(b'error list\r\n'), bytearray(b'exit\r\n')]) SetUpTestInjections(controller_serial=pty) comm = MasterCommunicator(init_master=False) comm.start() comm.start_maintenance_mode() pty.fd.write(bytearray(b'OK')) self.assertRaises(InMaintenanceModeException, comm.do_command, action, fields) self.assertEqual(bytearray(b'OK'), comm.get_maintenance_data()) comm.send_maintenance_data(bytearray(b'error list\r\n')) pty.fd.write(bytearray(b'the list\n')) self.assertEqual(bytearray(b'the list\n'), comm.get_maintenance_data()) comm.stop_maintenance_mode()
def test_passthrough_with_commands(self): action = master_api.basic_action() fields = {'action_type': 1, 'action_number': 2} pty = DummyPty([action.create_input(1, fields), action.create_input(2, fields), action.create_input(3, fields)]) SetUpTestInjections(controller_serial=pty) comm = MasterCommunicator(init_master=False) comm.enable_passthrough() comm.start() pty.master_reply(bytearray(b'hello') + action.create_output(1, {'resp': 'OK'})) self.assertEqual('OK', comm.do_command(action, fields)['resp']) self.assertEqual(bytearray(b'hello'), comm.get_passthrough_data()) pty.master_reply(action.create_output(2, {'resp': 'OK'}) + bytearray(b'world')) self.assertEqual('OK', comm.do_command(action, fields)['resp']) self.assertEqual(bytearray(b'world'), comm.get_passthrough_data()) pty.master_reply(bytearray(b'hello') + action.create_output(3, {'resp': 'OK'}) + bytearray(b' world')) self.assertEqual('OK', comm.do_command(action, fields)['resp']) self.assertEqual(bytearray(b'hello world'), comm.get_passthrough_data())