def test_assigning_script_to_wrong_button_fails_parsing(self): self.assertRaises( ConfigError, lambda: RobotConfig.from_string(''' { "robotConfig": [], "blocklyList": [ { "pythonCode": "", "assignments": { "buttons": [ {"id": 33, "priority": 0} ] } } ] }''')) self.assertRaises( ConfigError, lambda: RobotConfig.from_string(''' { "robotConfig": [], "blocklyList": [ { "pythonCode": "", "assignments": { "buttons": [ {"id": "nan", "priority": 0} ] } } ] }'''))
def test_empty_or_null_motors_are_not_configured(self): json = ''' { "robotConfig": { "motors": [ { "name": "M1", "type": 1, "side": 0 }, {}, null, { "name": "M4", "type": 1, "side": 1 } ] }, "blocklyList": [] }''' config = RobotConfig.from_string(json) self.assertEqual(Motors.RevvyMotor, config.motors[1]) self.assertEqual(None, config.motors[2]) self.assertEqual(None, config.motors[3]) self.assertEqual(Motors.RevvyMotor, config.motors[4])
def test_scripts_can_be_assigned_to_multiple_analog_channels(self): json = ''' { "robotConfig": [], "blocklyList": [ { "pythonCode": "{SOURCE}", "assignments": { "analog": [ {"channels": [0, 1], "priority": 1} ] } } ] }'''.replace('{SOURCE}', b64_encode_str("some code")) config = RobotConfig.from_string(json) self.assertEqual(1, len(config.controller.analog)) script_name = '[script 0] analog channels 0, 1' script_descriptor = config.controller.analog[0]['script'] self.assertListEqual([0, 1], config.controller.analog[0]['channels']) self.assertEqual(script_name, script_descriptor.name) self.assertTrue(callable(script_descriptor.runnable)) self.assertEqual(1, script_descriptor.priority)
def test_empty_or_null_sensors_are_not_configured(self): json = ''' { "robotConfig": { "sensors": [ { "name": "S1", "type": 1 }, {}, null, { "name": "S4", "type": 2 } ] }, "blocklyList": [] }''' config = RobotConfig.from_string(json) self.assertEqual(Sensors.Ultrasonic, config.sensors[1]) self.assertEqual(None, config.sensors[2]) self.assertEqual(None, config.sensors[3]) self.assertEqual(Sensors.BumperSwitch, config.sensors[4])
def test_scripts_can_be_assigned_to_multiple_buttons(self): json = ''' { "robotConfig": [], "blocklyList": [ { "pythonCode": "{SOURCE}", "assignments": { "buttons": [ {"id": 0, "priority": 2}, {"id": 2, "priority": 0} ] } } ] }'''.replace('{SOURCE}', b64_encode_str("some code")) config = RobotConfig.from_string(json) script_names = ['[script 0] button 0', '[script 1] button 2'] self.assertEqual(script_names[0], config.controller.buttons[0].name) self.assertEqual(script_names[1], config.controller.buttons[2].name) self.assertTrue(callable(config.controller.buttons[0].runnable)) self.assertTrue(callable(config.controller.buttons[2].runnable)) self.assertEqual(2, config.controller.buttons[0].priority) self.assertEqual(0, config.controller.buttons[2].priority)
def test_scripts_can_be_assigned_to_multiple_analog_channels(self): json = ''' { "robotConfig": [], "blocklyList": [ { "pythonCode": "{SOURCE}", "assignments": { "analog": [ {"channels": [0, 1], "priority": 1} ] } } ] }'''.replace('{SOURCE}', b64_encode_str("some code")) config = RobotConfig.from_string(json) self.assertEqual(1, len(config.controller.analog)) self.assertEqual('user_script_0', config.controller.analog[0]['script']) self.assertListEqual([0, 1], config.controller.analog[0]['channels']) self.assertEqual('some code', config.scripts['user_script_0']['script']) self.assertEqual(1, config.scripts['user_script_0']['priority'])
def test_scripts_can_be_assigned_to_multiple_buttons(self): json = ''' { "robotConfig": [], "blocklyList": [ { "pythonCode": "{SOURCE}", "assignments": { "buttons": [ {"id": 0, "priority": 2}, {"id": 2, "priority": 0} ] } } ] }'''.replace('{SOURCE}', b64_encode_str("some code")) config = RobotConfig.from_string(json) self.assertEqual('user_script_0', config.controller.buttons[0]) self.assertEqual('user_script_1', config.controller.buttons[2]) self.assertEqual('some code', config.scripts['user_script_0']['script']) self.assertEqual('some code', config.scripts['user_script_1']['script']) self.assertEqual(2, config.scripts['user_script_0']['priority']) self.assertEqual(0, config.scripts['user_script_1']['priority'])
def test_scripts_can_be_assigned_to_every_type_at_once(self): json = ''' { "robotConfig": [], "blocklyList": [ { "pythonCode": "{SOURCE}", "assignments": { "buttons": [{"id": 1, "priority": 0}], "analog": [{"channels": [0, 1], "priority": 1}], "background": 3 } } ] }'''.replace('{SOURCE}', b64_encode_str("some code")) config = RobotConfig.from_string(json) self.assertEqual(1, len(config.background_scripts)) self.assertEqual('user_script_0', config.controller.analog[0]['script']) self.assertEqual('user_script_1', config.controller.buttons[1]) self.assertEqual('user_script_2', config.background_scripts[0]) self.assertEqual('some code', config.scripts['user_script_0']['script']) self.assertEqual('some code', config.scripts['user_script_1']['script']) self.assertEqual('some code', config.scripts['user_script_2']['script']) self.assertEqual(0, config.scripts['user_script_1']['priority']) self.assertEqual(1, config.scripts['user_script_0']['priority']) self.assertEqual(3, config.scripts['user_script_2']['priority'])
def test_scripts_can_be_assigned_to_every_type_at_once(self): json = ''' { "robotConfig": [], "blocklyList": [ { "pythonCode": "{SOURCE}", "assignments": { "buttons": [{"id": 1, "priority": 0}], "analog": [{"channels": [0, 1], "priority": 1}], "background": 3 } } ] }'''.replace('{SOURCE}', b64_encode_str("some code")) config = RobotConfig.from_string(json) self.assertEqual(1, len(config.background_scripts)) script_names = [ '[script 0] analog channels 0, 1', '[script 1] button 1', '[script 2] background' ] self.assertEqual(script_names[0], config.controller.analog[0]['script'].name) self.assertEqual(script_names[1], config.controller.buttons[1].name) self.assertEqual(script_names[2], config.background_scripts[0].name) self.assertTrue( callable(config.controller.analog[0]['script'].runnable)) self.assertTrue(callable(config.controller.buttons[1].runnable)) self.assertTrue(callable(config.background_scripts[0].runnable)) self.assertEqual(1, config.controller.analog[0]['script'].priority) self.assertEqual(0, config.controller.buttons[1].priority) self.assertEqual(3, config.background_scripts[0].priority)
def test_valid_config_needs_robotConfig_and_blocklies_keys(self): with self.subTest("Blockly only"): config = RobotConfig.from_string('{"blocklies": []}') self.assertIsNone(config) with self.subTest("Robot Config only"): config = RobotConfig.from_string('{"robotConfig": []}') self.assertIsNone(config) with self.subTest("Both"): config = RobotConfig.from_string( '{"robotConfig": [], "blocklyList": []}') self.assertIsNotNone(config) with self.subTest("Both, lowercase"): config = RobotConfig.from_string( '{"robotconfig": [], "blocklylist": []}') self.assertIsNotNone(config)
def __init__(self, interface: RevvyControl, revvy, sound_paths, sw_version, default_config=None): print("RobotManager: __init__()") self.needs_interrupting = True self._configuring = False self._robot = Robot(interface, sound_paths, sw_version) self._interface = interface self._ble = revvy self._default_configuration = default_config or RobotConfig() self._status_update_thread = periodic(self._update, 0.02, "RobotStatusUpdaterThread") self._background_fn_lock = Lock() self._background_fns = [] rc = RemoteController() rcs = RemoteControllerScheduler(rc) rcs.on_controller_detected(self._on_controller_detected) rcs.on_controller_lost(self._on_controller_lost) self._remote_controller = rc self._remote_controller_scheduler = rcs self._remote_controller_thread = create_remote_controller_thread(rcs) self._resources = { 'led_ring': Resource(), 'drivetrain': Resource(), 'sound': Resource(), **{ 'motor_{}'.format(port.id): Resource() for port in self._robot.motors }, **{ 'sensor_{}'.format(port.id): Resource() for port in self._robot.sensors } } revvy['live_message_service'].register_message_handler( self._remote_controller_scheduler.data_ready) revvy.on_connection_changed(self._on_connection_changed) self._scripts = ScriptManager(self) self._config = self._default_configuration self._status_code = RevvyStatusCode.OK self.exited = False
def test_named_ports_can_be_accessed_via_robot_interface(self): """This test case verifies that PortCollection and RobotConfig use the same port numbering conventions""" config_str = '''{ "robotConfig": { "motors": [ { "name": "M1", "type": 1, "reversed": 0, "side": 0 }, { "name": "M2", "type": 2, "reversed": 1, "side": 1 } ], "sensors": [ { "name": "S1", "type": 1 }, { "name": "S2", "type": 2 }, { "name": "S3", "type": 0 }, { "name": "S4", "type": 1 } ] }, "blocklyList": [] }''' config = RobotConfig.from_string(config_str) motors = PortCollection([2, 3, 5, 7]) motors.aliases.update(config.motors.names) sensors = PortCollection([3, 5, 7, 9]) sensors.aliases.update(config.sensors.names) self.assertEqual(3, motors["M2"]) self.assertEqual(9, sensors["S4"])
def test_scripts_without_code_or_script_name_fail(self): self.assertRaises( ConfigError, lambda: RobotConfig.from_string(''' { "robotConfig": [], "blocklyList": [ { "assignments": { "buttons": [ {"id": 0, "priority": 2}, {"id": 2, "priority": 0} ] } } ] }'''))
def test_lower_case_script_name_is_accepted(self): json = ''' { "robotConfig": [], "blocklyList": [ { "builtinscriptname": "drive_2sticks", "assignments": { "analog": [{"channels": [0, 1], "priority": 2}] } } ] }''' config = RobotConfig.from_string(json) self.assertEqual('user_script_0', config.controller.analog[0]['script']) self.assertTrue(callable(config.scripts['user_script_0']['script']))
def on_message_updated(self, message: ReceivedLongMessage): message_type = message.message_type self._log(f'Received message: {message_type}') if message_type == LongMessageType.TEST_KIT: test_script_source = message.data.decode() self._log(f'Running test script: {test_script_source}') script_descriptor = ScriptDescriptor( "test_kit", str_to_func(test_script_source), 0) def start_script(): self._log("Starting new test script") handle = self._robot._scripts.add_script(script_descriptor) handle.on_stopped(partial(self._robot.configure, None)) # start can't run in on_stopped handler because overwriting script causes deadlock self._robot.run_in_background(handle.start) self._robot.configure(empty_robot_config, start_script) elif message_type == LongMessageType.CONFIGURATION_DATA: message_data = message.data.decode() self._log(f'New configuration: {message_data}') try: parsed_config = RobotConfig.from_string(message_data) if self._ignore_config: self._log('New configuration ignored') else: self._robot.configure(parsed_config, self._robot.start_remote_controller) except ConfigError: self._log(traceback.format_exc()) elif message_type == LongMessageType.FRAMEWORK_DATA: self._robot.robot.status.robot_status = RobotStatus.Updating self._progress.set_indeterminate() self._robot.request_update() elif message_type == LongMessageType.ASSET_DATA: extract_asset_longmessage(self._storage, self._asset_dir)
def test_type0_objects_dont_require_additional_keys(self): json = ''' { "robotConfig": { "motors": [ { "type": 0 } ], "sensors": [ { "type": 0 } ] }, "blocklyList": [] }''' config = RobotConfig.from_string(json) self.assertIsNotNone(config)
def test_lower_case_pythoncode_is_accepted(self): json = ''' { "robotConfig": [], "blocklyList": [ { "pythoncode": "{SOURCE}", "assignments": { "background": 3 } } ] }'''.replace('{SOURCE}', b64_encode_str("some code")) config = RobotConfig.from_string(json) self.assertEqual(1, len(config.background_scripts)) self.assertEqual('user_script_0', config.background_scripts[0]) self.assertEqual('some code', config.scripts['user_script_0']['script']) self.assertEqual(3, config.scripts['user_script_0']['priority'])
def test_scripts_can_be_configured_to_run_in_background(self): json = ''' { "robotConfig": [], "blocklyList": [ { "pythonCode": "{SOURCE}", "assignments": { "background": 3 } } ] }'''.replace('{SOURCE}', b64_encode_str("some code")) config = RobotConfig.from_string(json) self.assertEqual(1, len(config.background_scripts)) script_name = '[script 0] background' self.assertEqual(script_name, config.background_scripts[0].name) self.assertEqual(3, config.background_scripts[0].priority)
def test_motor_side_and_reversed_is_ignored_for_normal_motors(self): json = ''' { "robotConfig": { "motors": [ { "name": "M1", "type": 1, "reversed": 0, "side": 0 }, { "name": "M2", "type": 1, "reversed": 1, "side": 0 }, { "name": "M3", "type": 1, "reversed": 0, "side": 1 }, { "name": "M4", "type": 1, "reversed": 1, "side": 1 } ] }, "blocklyList": [] }''' config = RobotConfig.from_string(json) self.assertEqual(Motors.RevvyMotor, config.motors[1]) self.assertEqual(Motors.RevvyMotor, config.motors[2]) self.assertEqual(Motors.RevvyMotor, config.motors[3]) self.assertEqual(Motors.RevvyMotor, config.motors[4])
def test_lower_case_pythoncode_is_accepted(self): json = ''' { "robotConfig": [], "blocklyList": [ { "pythoncode": "{SOURCE}", "assignments": { "background": 3 } } ] }'''.replace('{SOURCE}', b64_encode_str("some code")) config = RobotConfig.from_string(json) self.assertEqual(1, len(config.background_scripts)) script_name = '[script 0] background' self.assertEqual(script_name, config.background_scripts[0].name) self.assertTrue(callable(config.background_scripts[0].runnable)) self.assertEqual(3, config.background_scripts[0].priority)
def test_builtin_scripts_can_be_referenced(self): json = ''' { "robotConfig": [], "blocklyList": [ { "builtinScriptName": "drive_2sticks", "assignments": { "analog": [{"channels": [0, 1], "priority": 2}] } } ] }''' config = RobotConfig.from_string(json) script_name = '[script 0] analog channels 0, 1' self.assertEqual(script_name, config.controller.analog[0]['script'].name) self.assertTrue( callable(config.controller.analog[0]['script'].runnable)) self.assertEqual(drive_2sticks, config.controller.analog[0]['script'].runnable)
def test_valid_config_needs_robotConfig_and_blocklies_keys(self): with self.subTest("Blockly only"): self.assertRaises( ConfigError, lambda: RobotConfig.from_string('{"blocklies": []}')) with self.subTest("Robot Config only"): self.assertRaises( ConfigError, lambda: RobotConfig.from_string('{"robotConfig": []}')) # these should not raise ConfigError with self.subTest("Both"): RobotConfig.from_string('{"robotConfig": [], "blocklyList": []}') with self.subTest("Both, lowercase"): RobotConfig.from_string('{"robotconfig": [], "blocklylist": []}')
def test_sensors_are_parsed_as_list_of_sensors(self): json = ''' { "robotConfig": { "sensors": [ { "name": "S1", "type": 1 }, { "name": "S2", "type": 2 }, { "name": "S3", "type": 0 }, { "name": "S4", "type": 0 } ] }, "blocklyList": [] }''' config = RobotConfig.from_string(json) self.assertEqual(Sensors.Ultrasonic, config.sensors[1]) self.assertEqual(Sensors.BumperSwitch, config.sensors[2]) self.assertEqual(None, config.sensors[3]) self.assertEqual(None, config.sensors[4]) self.assertEqual(1, config.sensors.names["S1"]) self.assertEqual(2, config.sensors.names["S2"]) # not configured ports does not have names self.assertNotIn("S3", config.sensors.names) self.assertNotIn("S4", config.sensors.names)
def test_not_valid_config_is_ignored(self): config = RobotConfig.from_string('not valid json') self.assertIsNone(config)
def test_not_valid_config_is_ignored(self): self.assertRaises(ConfigError, lambda: RobotConfig.from_string('not valid json'))
def test_motors_are_parsed_as_list_of_motors(self): json = ''' { "robotConfig": { "motors": [ { "name": "M1", "type": 0, "reversed": 0, "side": 0 }, { "name": "M2", "type": 2, "reversed": 0, "side": 0 }, { "name": "M3", "type": 2, "reversed": 1, "side": 0 }, { "name": "M4", "type": 1, "reversed": 1 }, { "name": "M5", "type": 2, "reversed": 0, "side": 1 }, { "name": "M6", "type": 2, "reversed": 1, "side": 1 } ] }, "blocklyList": [] }''' config = RobotConfig.from_string(json) self.assertEqual(None, config.motors[1]) # drivetrain left self.assertEqual(Motors.RevvyMotor_CCW, config.motors[2]) # normal left self.assertEqual(Motors.RevvyMotor, config.motors[3]) # reversed left # drivetrain right self.assertEqual(Motors.RevvyMotor, config.motors[5]) # normal right self.assertEqual(Motors.RevvyMotor_CCW, config.motors[6]) # reversed right self.assertEqual(Motors.RevvyMotor, config.motors[4]) # motor, no 'side', no 'reversed' self.assertListEqual([2, 3], config.drivetrain['left']) self.assertListEqual([5, 6], config.drivetrain['right']) self.assertNotIn( "M1", config.motors.names) # not configured port does not have name self.assertEqual(2, config.motors.names["M2"]) self.assertEqual(3, config.motors.names["M3"]) self.assertEqual(4, config.motors.names["M4"]) self.assertEqual(5, config.motors.names["M5"]) self.assertEqual(6, config.motors.names["M6"])